By Rasmus Graversen, CTO created on during Software development
For the last 3 months, we have been working on upgrading SmartWeb to PHP 7. It has been a much smoother process than upgrading from PHP 5.3 to 5.6, which we did years ago. This article will describe our process of upgrading from PHP 5.6 to PHP 7.0 – why we did it, what problems we faced and what we learned.
This graph talks for itself:
Load time of around 1.100 ms down to 600 ms. A customer going from PHP 5.6 to PHP 7.
The speed boost gained from switching to PHP 7 is enormous. From early experiments we estimated a decrease in load times up to 50 %. Now looking at the result, PHP 7 fully lives up to those expectations.
For our customers this is great news. Speed has a direct influence on conversion rates. Amazon claimed in 2009 that every 100 milliseconds faster will result in 1 % more sales. Several other big companies have made similar claims and Google also favors faster loading e-commerce sites both in organic searches and in ads.
Depending on the stores size, the theme customization and where in the store you are, the switch to PHP 7 can give our customers up to 1 second of speed improvement. We cannot stop celebrating after giving our users this speed gain which will hopefully lead to better conversion rates.
To be honest, the speed gain was our main goal for updating to PHP 7. However, a lot of other benefits are gained from the update. New features which will improve the way we work with our code. Read about them here.
First, we set up new docker containers for our admin and frontend environments with the possibility to run PHP 7 in development. Running our development environment in docker containers is a powerful tool since it enabled us to create the new containers and have them running in a matter of minutes.
Before spinning up the new PHP 7 development environment we already expected the environment to fail due to known backwards compatibility issues with PHP 5.6 (http://php.net/manual/en/migration70.incompatible.php), which we knew were present in the codebase.
With this list in mind, we started working with the following issues:
All ext/mysql functions were removed
Some of our old codebase uses the MySQL extension, which with the update to PHP 7, goes from deprecated to removed. We are already in the process of switching to PDO_MYSQL (http://php.net/manual/en/ref.pdo-mysql.php) but this is still a work in progress. We have chosen to focus on specific parts of the system leaving out some others, mainly the administration. The reason for this is that we are already working on a new core for the administration which will be written from scratch. Because of this, we did not want to change the old codebase and waste a lot of resources.
This made us look at another option; creating wrappers, or so-called shims, for the MySQL functions.
As stated in Wikipedia: In computer programming, a shim is a small library that transparently intercepts API calls and changes the arguments passed, handles the operation itself, or redirects the operation elsewhere.
We tried out the approach using a slightly modified version of the dshafik/php7-mysql-shim package (https://github.com/dshafik/php7-mysql-shim) which we included on a global system level.
Basically, the MySQL shims solve the problem of the removed MySQL functionality by creating equally named functions which instead pass on the calls to the corresponding function for MySQLi (http://php.net/manual/en/book.mysqli.php).
The introduction of these shims immediately solved the problem of backward incompatibility and meant that in theory we did not have to introduce any changes to the existing codebase.
Since the shims were available as a composer package which we could use with minimal modifications the most time-consuming part of the upgrade has been extensive testing of existing functionality to make sure the switch of something as fundamental as database connectivity did not introduce errors.
During the testing we found a few instances of compatibility issues in older system code. However, they were all due to outdated or illogical use of the MySQL functions, and could be corrected with minimal effort.
All in all, the usage of MySQL to MySQLi shims have allowed us to avoid rewriting a part of the older code base, which would have been a waste of resources since we are already working on its replacement.
All ereg* functions removed
As with MySQL, some older parts of the system make use of the ereg functions for making regular expression matches. For the same reasons as mentioned in the above section, we chose to make use of shims for these too. We went with the bbrala/php7-ereg-shim package, available at Github (https://github.com/bbrala/php7-ereg-shim).
PHP 7 also introduces a couple of logical changes to the language. We encountered a few instances of the following cases:
- New objects cannot be assigned by reference
- JSON extension replaced with JSOND
- Changes to the handling of indirect variables, properties, and methods
The trickiest of these to solve for us has been the switch to JSOND since the effect was not immediately obvious for us, and are not all mentioned in the PHP 7 backwards incompatibility change notes. The most obvious potential problem is mentioned “[…] Finally, an empty string is no longer considered valid JSON.” And was accounted for by making a few changes. However, during testing, we still ended up with empty return data from some of our ajax-services due to the new JSOND engine. We later discovered that it cannot handle NaN (not-a-number) values and were able to remedy this fact with a minor change to a single handler.
Examples of the other two logical changes can be found in the PHP 7 backwards incompatibility change notes and we will not go into detail with them here, since we only encountered them in a couple of lines and they were easily fixed.
We wanted to make the deployment process of PHP 7 as smooth as possible. To achieve this, we wanted to get the code making the switch possible into production before performing the actual switch of our services from PHP 5.6 to PHP 7.
For the logical changes, we were able to rewrite the code to be compatible with PHP 7 without changing their functionality in PHP 5.6.
For the shims, we used the version_compare function to create a simple check which only includes the shims if the platform is running PHP 7 (including them in PHP 5.6 would throw a fatal error since the MySQL functions already exist).
We deployed the logical changes and the (dormant) PHP 7 shims and were now ready for the switch.
To minimize the impact of any potential problems on the production environment we have deployed the PHP 7 update in stages. First deploying it to dedicated servers containing only a handful of shops. After dealing with a few bugs which were not encountered on the test environment we began deploying the update to servers containing a bigger number of customers as well as our API and other servers.
All in all, the deployment process took around 5 weeks.
Upgrading our platform to PHP 7 has been an interesting process, and much thought went into choosing the right approaches for the challenges and obstacles encountered. We also learned a lot during the upgrade.
- Get rid of deprecated features as soon as possible. Use full log levels in development and do not use deprecated features in new code. When it makes sense, also update old code.
- Shims can be a solution for faster upgrade. We wanted to give our customers and ourselves the benefits of PHP 7. Instead of making our old codebase a reason not to upgrade, shims have been a workable solution for us.
- Upgrading in steps is good. Making the code work for both PHP 7 and 5.6 was good, since it made it easy to switch. And rolling PHP 7 out in production in steps was also a good process.