Migrating PHP 7.4 Code to 8.1 on Platform.sh

Assumptions

You will need:

End of the Road for PHP7.4

The end-of-life (EOL) for PHP 7.4 was Monday, Nov 28, 2022. If you’re like many of us, that date snuck up on much faster than we anticipated. While the PHP 7.4 image won’t be removed from our services, it is no longer supported and has been moved to Deprecated status. At this point, what do you do?

You could remain on PHP 7.4 but there are several benefits to updating. The biggest are security and support. As we move farther and farther away from the EOL date, attackers will turn their focus to PHP 7.4 knowing that any vulnerabilities they discover will go unpatched in the majority of systems. Staying on PHP 7.4 drastically increases the risk of your site being compromised in the future. In a similar vein, finding support for issues you encounter with PHP 7.4 will become increasingly more difficult. In addition, you will most likely begin to encounter compatibility issues with third-party code/packages as they update their code to be compatible with later versions and drop support for 7.4. You’ll also be missing out on significant speed and performance improvements introduced in 8.0 and further improved in 8.1. But upgrading all that legacy code is daunting!

Where to start?

Luckily, PHP provides an official migration guide from PHP 7.4 to 8.0 to get you started (and an 8.0 to 8.1 migration guide as well). Be sure to read through the Backward Incompatible Changes and Deprecated Features sections. While these guides are incredibly handy, you may very well have tens of thousands of lines of code to check, some of which you may have inherited. Luckily we have some options to help pinpoint potential problematic areas in the migration.

PHPCodeSniffer + PHPCompatibility sniffs

PHPCodeSniffer (PCS) is a package for syntax checking of PHP Code. It checks your code against a collection of defined rules (aka “sniffs”) referred to as “standards”. PHPCodeSniffer ships with a collection of standards you can use including PEAR, PSR1, PSR2, PSR12, Squiz, and Zend. Luckily, you can write your own collection of sniffs to define any set of rules you like.

PHPCompability has entered the chat

PHPCompatibility “is a set of sniffs for PHP CodeSniffer that checks for PHP cross-version compatibility” allowing you to test your codebase for compatibility with different versions of PHP, including PHP 8.0 and 8.1. This means we can use PHPCodeSniffer to scan our codebase, applying the rules from PHPCompability to sniff out any incompatibilities with PHP 8.1 that might be present.

Before we continue…

The release of PHP8.2 is just around the corner (currently scheduled for December 8, 2022). While I encourage you to begin looking over the official 8.1 to 8.2 migration guide and begin making plans to upgrade, most of the checkers I mention in this post have not completed full support for 8.2 at this time. For those reasons, I’ll be focusing on migrating the code to PHP8.1, and not 8.2.

In the process of writing this post, I discovered PHPCompatiblity has a known issue when checking for compatibility with PHP 8.0/8.1 where it will report issues that should be Errors as Warnings. The only work-around for now is to use the develop branch for PHPCompatibility instead of master. While they state it is stable, please be aware that in this post I’m using the non-stable branch. You may want to weigh the pros and cons of using the develop branch before implementing it anywhere else than in a local development environment. While I found PCS+PHPCompatibility to be the most straight-forward and comprehensive solution for checking for incompatible code, if you do not want to use a non-stable version of PCS, see the Alternative Options section below.

For the purposes of this post, I’ll be using the 1.4.6 version of SimpleSAMLphp to test for incompatibilities. This is a six-year-old version of the code base. I do this not to pick on SimpleSAMLphp, but because I wanted something that would definitely have some errors. As it turns out, all of the platform.sh code I tested, as well as my own code was already compatible with PHP8.1 and required no changes.

Let’s get started

To get started, first clone your codebase, and then create a new branch. You’ll now need to decide if you want to install the dependencies and run the scans on your local machine or into a local development environment using something like DDEV, Lando, Docksal, etc. For the purposes of this post, I’ll be using DDEV. I suggest using a local development environment vs running directly on your local machine because while it’s not required to use the version of PHP you want to test against, for the best results it is recommended you do so. If you don’t have PHP installed, or don’t have the target version installed, a local development environment allows you to create an ephemeral environment with exactly what you need without changing your machine.

After setting up your environment for PHP 8.1, at a terminal prompt (in my case, I’ve run ddev start and once the containers are available, shell into the web app using ddev ssh), we need to add these new packages so we can use them to test with. I’ll be adding them with composer, however, there are multiple ways to install them if you would prefer to do so differently. If your codebase isn’t already using composer, you’ll need to dd composer init before continuing.

Because we’ll be using the develop branch of PHPCompatibility there are a couple of extra steps we will need to do that aren’t in the regular installation instructions. First is that the develop branch of PHPCompatibility requires an alpha version of phpcsstandards/phpcsutils. Because it is marked as alpha, we’ll need to let composer know this one package is OK to install even though it is below our minimum stability requirements.

$ composer require --dev phpcsstandards/phpcsutils:"^1.0@dev"

Next we’ll install PHPCompatibility targeting the develop branch

$ composer require --dev phpcompatibility/php-compatibility:dev-develop

The develop branch also installs dealerdirect/phpcodesniffer-composer-installer so we don’t need to add it manually, or direct PCS to this new standard.

To verify our new standards are installed, we’ll have PCS display the standards it is aware of.

$ phpcs -i
The installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz, Zend, PHPCompatibility, PHPCS23Utils and PHPCSUtils

Now that we know our standards are available, we can have PCS scan our code. To instruct PCS to use a specific standard, we’ll use the --standard option and tell it to use PHPCompatibility. However, we also need to tell PHPCompatibility which PHP version we want to test against. For that we’ll use PCS’ --runtime-set option and pass it the key testVersion and value of 8.1.

Before we start the scan, the one issue remaining is that code we want to scan is in the root of the project (.) but the vendor directly is also in the project root. We don’t want the code in vendor scanned, as those aren’t packages we necessarily control. PCS allows us to tell it to not scan files/directories with the --ignore option. Finally, we want to see the progress as PCS parses the file so we’ll pass in the -p option.

Putting it all together:

$ phpcs -p . --standard=PHPCompatibility --runtime-set testVersion 8.1 --ignore=*/vendor/*

This kicks off PCS which will output its progress as it scans through your project’s code. W indicates Warnings, and E indicates Errors. At the end of the scan it will output:

  • A full report with the file containing the issue
  • The line number where the issue occurs
  • Whether the issue is a Warning or an Error
  • The specific issue discovered

In general, Errors are things that will cause a fatal error in PHP 8.1 and will need to be fixed before you can migrate. Warnings can be things that have been deprecated in 8.0/8.1 but not yet removed, or issues that PCS ran into while trying to parse the file.

asciicast

Given that the report might be long, and is output all at once into your terminal, there are numerous options for changing the information that is included in the report, as well as multiple reporting formats.

As you begin to fix your code, you can rerun the report as many times as needed. However, at some point you’ll need to test the code on an actual PHP8.1 environment with real data. Luckily, Platform.sh makes that extremely easy for you to do!

Assumptions

The following assumes you have already installed the Platform.sh CLI, associated the local instance of your project with your Platform.sh project and you have checked out the branch you want to use for testing the code changes.

Create a PHP8.1 development version of your Production Website

Before we create the new environment, we’ll need to update composer to use PHP8.1 to make sure there’s nothing locking/pinning us to an older PHP version, and to make sure we get newer versions of dependencies that are compatible with PHP8.1. You can either add

"platform": {
    "php": "8.1"
}

To the “config” property in composer.json and run composer update, or since we already have our environment set up for PHP 8.1, you can do it there. After running composer update, open .platform.app.yaml and change the PHP type from 7.4 to 8.1.

Add both .platform.app.yaml and composer.lock (and possibly composer.json) to git’s stage.

$ composer add .platform.app.yaml composer.lock

Commit the changes

$ git commit -m "Updating php type to 8.1 to test code migration"

Push your branch to platform.sh and activate it.

$ platform environment:push –activate

And that’s it! Platform will create a new environment based on this branch using the PHP8.1 image!

Why doesn’t everything work like this?

Once the environment is deployed, the Platform.sh CLI will report the URLs for the new environment. If for some reason they scroll by, or you don’t see them, you can retrieve them by using the Platform.sh CLI url command and selecting the URL you want to open in your browser.

Now you can run any of your standard testing, including your blackfire.io tests and assertions against this environment to make sure all parts are working as you expect. If any issues are discovered you can access the error.log, app.log, and the php.access.log by using the Platform.sh CLI log command.

There’s too much to fix!!!

Now that you have a solid idea of what needs to be updated before you can migrate, you might be facing an incredible amount of work ahead of you. Luckily, you have some options to help you out. PCS ships with a code fixer called PHP Code Beautifier and Fixer (phpcbf). Running phpcbf is almost identical to running phpcs and most of the options are identical. The other option is Rector. Usage of these tools is beyond the scope of this post, but as with any automation you’ll want to test and verify before promoting changes to production.

Alternative Options

If for any reason you don’t feel comfortable using a non-stable version of PCS, you do have other options for checking your code.

Phan

Phan is a static code analyzer for PHP. It offers multiple levels of analysis and allows for incrementally strengthening that analysis.

“Static analysis needs to be introduced slowly if you want to avoid your team losing their minds.”

Phan doesn’t target just compatibility with newer versions, it can highlight areas of code that will error in later versions. However, there are some caveats when using Phan for checking compatibility:

  • Slower than PCS+PHPCompatibility.
  • Phan requires the ast php extension which is not available by default on Platform.sh (or in DDEV). You’ll need to install it in your local development environment and add it to your php.ini file. Alternatively, you can use the --allow-polyfill-parser option, but it is considerably slower.
  • Phan’s default reporting output isn’t as easy to read as other options
  • I came across an issue where if your code base sets a different vendor directory via composer’s [config:vendor-dir](https://getcomposer.org/doc/06-config.md#vendor-dir) option, it will error out stating it can’t find certain files in the vendor directory
  • As mentioned, Phan analyzes much more than just PHP8.1 compatibility. While certainly a strength in other situations, if your goal is to migrate from 7.4 to 8.1 as quickly as possible, you will have to parse through errors that are unrelated to version compatibility.
  • Requires you run it on the PHP version you want to target

PHPStan

Similar to Phan, PHPStan is a static code analyzer for PHP that promises to “find bugs without writing tests.” And a similar set of caveats apply:

  • Slower than either PCS or Phan
  • Analyzes much more than just PHP8.1 compatibility so depending on your current codebase, you will have to possibly parse through a bunch of errors that are unrelated to version compatibility
  • Requires you run it on the PHP version you want to target

PHP Parallel Lint

A very fast PHP linter that can lint your codebase for issues, but can also check for deprecations. While it is exceptionally fast, it is only a linter, and therefore can only surface deprecations that are thrown at compile time, not at runtime. In my example code, it only found 2 deprecations vs the 960 deprecations PCS uncovered.

Summary

Code migrations, while never fun, are crucial to minimizing organizational risk. Platform.sh gives you the flexibility to test your code using the same data and configurations as your production site, but in a siloed environment. Combine this with the tools above, and you have everything you need for a strong, efficient code migration.

2 Likes