decorative image for blog showing a series of arrows pointing to php 8.2
November 21, 2023

Upgrading to PHP 8.2: Deprecations and Changes to Watch

Migration

Planning an upgrade to PHP 8.2? You're not alone. With PHP 7.4 (and PHP 8.0 soon) end of life, many teams are looking for the best upgrade path for their PHP-based applications.

Unfortunately, migrations from 7.4 might not be as easy as anticipated. With a number of breaking changes and deprecations that teams will need to account for when upgrading or migrating, it's good to go into that process with a full understanding of those changes, and the impact they will have on your codebase.

In this blog, we talk through the key considerations your team will need to make when planning a move to PHP 8.2, including when it's in your best interest to get PHP LTS, or seek out migration services.

Considerations When Planning a PHP 8.2 Upgrade or Migration

Before you get started on a migration or upgrade to PHP 8.2, you need to consider a number of things. First among them, your ability to complete such a project and the potential roadblocks you can expect along the way.

Lack of Resources

Given the realities that modern business environments must deal with, the primary limited resources are time and personnel. Even teams with more than sufficient experience to conduct an upgrade generally prioritize new feature development and bug fixing over PHP version migration. Often, new development must continue while the migration is underway, which can present significant challenges, especially for very large codebases.

Dependencies

A dependency is “something” that your application relies on to perform some of its functionality. Dependencies can be libraries or packages of reusable PHP source code; examples are a logger or an HTTP client.

Compiled extensions are also dependencies of your application because it or PHP packages might require them. Perhaps your application depends on the BCMath extension for arbitrary precision mathematics, or the OpenSSL extension for data encryption/decryption and other cryptographic operations.

In general, there are other types of dependencies to consider, as well. These include databases, web services provided by other servers under your control or managed by an external service provider, your operating system’s system packages, and the like.
PHP source code dependencies can be managed by dropping 3rd-party libraries into a directory and making the functionality available via include()/require() functions. We see this quite often in legacy applications, along with packages originally from PEAR. For some years, however, PHP source dependencies have been managed via Composer.

When planning and executing your PHP upgrade, you must consider the dependencies along with your application. Are the dependencies out of date? Are they actively maintained? Are versions compatible with PHP 8.2 available? If so, you may need to change your application to handle changes in the API of the package. If a PHP 8.2-compatble version is not available, are you willing to migrate the dependency and “own” it for the lifespan of the applications that depend on it, or is it better to find an actively maintained version and adapt your application to use the new one, instead?

Your application may currently depend on PHP extensions that do not exist in PHP 8.2. We see many PHP 5.x applications using mysql or mssql extensions.

How do you identify what dependencies your application has? One way to start is to scan for include()/require()/include_once()/require_once() statements, look for spl_autoload*() functions, and check your composer.json and composer.lock files.

Deprecations and Breaking Changes

What you're going to see when you cross a major version and run your PHP 7.4 application on PHP 8.2 are several breaking changes, warnings, and deprecations, because many deprecations introduced in PHP 7.4, 8.0, and 8.1 were implemented by the time PHP 8.2 arrived. You'll need to address them for your code to run as intended.
Some of these changes result in fatal errors while others yield changed behavior. You'll need to pay special attention to application output; data written to the filesystem, databases, and external services.
Your best friends will be your IDE and the PHP error log, and the migration guides on php.net for going from PHP 7.4 to 8.0, from 8.0 to 8.1, and from 8.1 to 8.2; the links are on the right side of this page . Make sure in your dev/test environment to set ini directive error_reporting to E_ALL.

And give some of our other blog articles a read:

Testing

Testing is absolutely critical for a migration or upgrade to be successful. If you don’t have automated tests, then be extremely diligent in your manual testing, and begin writing automated tests now. It can be argued that the first step of a migration would be to have a test suite.

If you don’t have time or sufficient staff, but you want tests, then prioritize an acceptance-type test suite over unit tests, so you are testing the behavior of the application as experienced by users or API clients.

Make sure your test suite is current and that all tests are passing. If some of your tests fail, but “everyone knows to ignore them,” those tests can’t catch behavior you might break during your migration. You can’t trust them, so fix them.

Run your tests in your migration environment; you may have to update test code, as well, but do so only to address PHP version compatibility and your testing framework’s new version requirements. See supported versions at the PHPUnit site here.

Version Control

Please, please, please use source code version control! Make one commit for each type of change across the application codebase. You will thank yourself later.

Planning and Executing a PHP 8.2 Migration

While there are a number of migration paths for PHP 8.2, the most common will be migrating from PHP 7.4 to 8.2.

PHP 7.4 to 8.2

Here is a small sampling of some of the changes between PHP 7.4 and 8.2. See the sections below for changes between 8.0 or 8.1 and 8.2. Breaking changes from 7.4 to 8.2 include:

Removed functions and behavior

  • get_magic_quotes_gpc() and get_magic_quotes_runtime(). Yes, they've been deprecated for _years_. It's time to delete them from your code.
  • Curly braces to access array and string offsets. These were removed in PHP 8.0. This change affects more codebases than you might expect.
  • money_format() - This was deprecated in PHP 7.4, and removed in PHP 8.0. Use NumberFormatter::formatCurrency(),
  • NumberFormatter::parseCurrency() instead.
    (real) type-cast, is_real() function - use (float) type-cast and is_float() instead. Again, these were removed in PHP 8.0.
  • export() method removed from Reflector interface - use a combination of class constructor and __toString() instead. Guess what? That's right; removed in 8.0!
  • Unbinding $this from non-static closures. PHP 8 guarantees that `$this` always exists inside non-static methods, because static calls to non-static methods are not permitted. This change provides the same guarantee that $this always exists for non-static closures declared inside non-static methods. What should you do? Identify the closure as static and avoid the binding of $this altogether.
  • FILTER_SANITIZE_MAGIC_QUOTES - use add_slashes(FILTER_SANITIZE_ADD_SLASHES) instead.
  • We see things like the following _far_ less often, but ... they could be in your code:
    • restore_include_path()
    • hebrevc() - use nl2br() on the result of hebrev() instead
    • convert_cyr_string() - use mb_convert_encoding(), iconv() or UConverter instead
    • ezmlm_hash() (Yes, we’ve seen this. Once. But we’ve seen it!)

Changed Behavior

  • Passing objects to array_key_exists() is not allowed as of PHP 8.0. To see if a property exists in an object, use property_exists() instead.
  • implode()/join() requires $glue and $pieces to be passed in the correct order ($glue, $pieces). You knew about this; now's the time to swap them.
  • Implemented the long-documented behavior for mb_strrpos(); PHP 8.x requires the 3rd parameter to be an integer; if you're passing encoding as the third argument instead of an offset, you have to stop that now.

New Deprecations

When migrating from PHP 7.4 to PHP 8.2, there are a few new deprecations to watch, including Dynamic class properties and "${var}" and "${expr}" style string interpolation.

Dynamic Class Properties

This is creating properties by simply assigning a value to them at run time, instead of declaring the properties in the class definition. This Customer class has no defined properties, but you can nevertheless create them dynamically:

 

class Customer {}
$customer = new Customer();
$customer->firstName = 'Jack';
var_dump($customer->firstName); // string(4) "Jack"

 

PHP 8.2 logs the following deprecation when it encounters the assignment:

Deprecated: Creation of dynamic property Customer::$firstName is deprecated in Command line code on line 3

 

We’ll cover the two most commonly appropriate ways to fix this. The most preferred is to declare the property:

class Customer {
   public string $firstName;
}

 

Second, and the one we see as a first iteration for large codebases, is to add the #[\AllowDynamicProperties] attribute to the class. Note that setting this one attribute takes care of all child classes of the class to which this attribute is applied.

#[\AllowDynamicProperties]
class Customer {}

 

"${var}" and "${expr}" Style String Interpolation.

"$var"/"{$var}" and "{${expr}}", should be used, instead. While a mere deprecation now, with the removal not occurring until PHP 9, it could affect a lot of code, so beginning to deal with it sooner rather than later is a good idea.

Planning and Executing a PHP 8.2 Upgrade

Upgrading within the PHP 8 major version (e.g. 8.0 to 8.2, or 8.1 to 8.2) removes many of the complexities of a migration. That said, there are still a number of changes to watch that can throw a wrench in your plans if you're not paying attention. Let's look at the most common upgrade path, PHP 8.1 to 8.2, first.

PHP 8.1 to 8.2 Upgrade

Straight from the php.net migration guide, you’ll likely become a little more exposed to:

  • PHP’s tentative return types in methods such as DateTime::createFromImmuntable(),
  • DateTimeImmutable::createFromMutable(), SplFileObject::hasChildren(), and SplFileObject::getChildren(),
  • ODBC and PDO_ODB escaping username and password when you pass a connection string,
  • Some methods in SPLFileInfo and SplFileObject now enforcing their signatures,
  • Some string functions no longer being local-sensitive: strtolower(), strtoupper(), stristr(), stripos(), strripos(), lcfirst(), ucfirst(), ucwords(), and str_ireplace(),
  • Changes to FilesystemIterator’s constructor; you now must explicitly set the FilesystemIterator::SKIP_DOTS flag if you want to exclude dot files . and ..
  • Those dynamic class properties we mentioned above. They won’t bite just yet, but address them now, and save yourself headaches later.

PHP 8.0 to 8.2 Upgrade

If you’re at PHP 8.0 today, you’re in a minority of users. More organizations moved to 8.1, delaying their migration from PHP 7.4 until 8.1 was available because of the increased effort and time needed to cross major version numbers, and they wished to avoid a quick succession of code updates.

Due to the differences between 7.4 and 8.0, you’ve already dealt with many of the changes discussed in the PHP 7.4 to 8.2 Migration section above and will face fewer BC breaks and deprecations. Your attention will be focused on changes such as:

  • Various restrictions on accessing the $GLOBALS variable
  • A deprecation in PHP 8.0 that was implemented in 8.1, optional parameters declared before required parameters in a function or method definition are implicitly treated as required. If you don’t fix the order, and call the function without passing all the arguments, PHP throws an ArgumentCountError
  • You will absolutely notice this one at runtime: from the migration guide, “most non-final internal methods now require overriding methods to declare a compatible return type, otherwise a deprecated notice is emitted during inheritance validation. In case the return type cannot be declared for an overriding method due to PHP cross-version compatibility concerns, a ReturnTypeWillChange attribute can be added to silence the deprecation notice.”
  • Numerous functions now take object instances instead of resources as the argument they operate upon. These changes will typically not affect end users that much, particularly if they are using a framework or library that abstracts access to those resources, but they can cause problems. You can read more about resource object promotion in our blogs on the features and changes found in PHP 8.0 and PHP 8.1.

Final Thoughts

Migrating or upgrading your PHP versions offers a host of benefits, ranging from better performance, to community patches and bug fixes, to access to latest language features. 

However, not all companies have the capacity to migrate PHP versions alongside the PHP community support lifecycle. Whether that's because of the size/complexity of the underlying application, or the ability to execute on those upgrades/migrations while keeping development priorities on track, sometimes teams need to find ways to either stay on a version longer, or find outside help to complete their upgrade. At Zend, we can help with both.

How to Get PHP LTS and Migration Services

If you're running end of life PHP and need help keeping your application patched against CVEs, or need experts to support your migration, Zend can help. Get details today via the links below.

See PHP LTS Options Explore Migration Services

Additional Resources