PHP 8.2 was released in December 2022 as the latest minor version in the PHP 8.x release cycle. It adds new features that build upon the capabilities of previous versions, further simplifying the development experience. In this article, we’ll tour each of the major changes and show how they’ll make your code easier to maintain.

Type System Improvements

PHP has been gradually evolving its type system towards a more strongly typed model over the past several releases. 8.2 includes two enhancements that allow types to be even more expressive.

Disjunctive Normal Form: Combined Unions and Intersections

Union and intersection types can now be combined anywhere a type is accepted, such as function parameters and return values. The complete type definition must be written using boolean disjunctive normal form notation (DNF). This means intersection types have to be wrapped in parentheses to be valid.

The following function allows you to pass either an array, or an object that implements both the Countable and Iterator interfaces:

This facilitates more powerful type definitions, similar to the example function shown above. Prior to PHP 8.2, you’d have to write two separate functions to achieve the same effect, one for each of the types in the union.

Standalone Types for Null and Booleans

The values true, false, and null are now accepted as standalone types. They can be used in any type definition to indicate that the specific value will be returned:

In realistic use cases, you’ll normally write these values as part of a union type. Handling errors by returning false instead of a regular value is a common pattern in both the PHP core and userland code, for example. Now you can properly describe this behavior in your return type definition:

Readonly Classes

Readonly properties were one of the headline features of PHP 8.1. They let you enforce immutability for class properties after their initial assignation:

In practice, many classes want all their properties to be readonly. Now you can mark the entire class as readonly, which lets you drop the readonly keyword from the individual properties.

Besides saving some repetitive typing, making a class readonly adds a few other constraints:

You cannot add any untyped properties to the class. Untyped properties aren’t supported as readonly properties, which readonly classes are syntactic sugar for. The class cannot contain static properties either, because these are similarly incompatible with the readonly keyword. The class cannot be extended by non-readonly children. Inheritance is permitted if the child also includes the readonly keyword. Dynamic properties cannot be created on instances of the class and the AllowDynamicProperties attribute is blocked from overriding this.

Readonly classes make it more convenient to write data transfer objects and other structures that are intended to be immutable. Their use is not obligatory though: you can still create partially mutable classes by continuing to use the readonly keyword on individual properties.

Enum Properties Can Be Consumed In Constant Expressions

Enum properties can now be used in constant expressions. The following code, which was not supported in the version of enums shipped with PHP 8.1, is now legal:

The value property of the enum’s Published case is accessed without throwing an error, because PHP’s engine is aware its value can never change.

Traits Can Define Constants

It’s now possible for traits to define constants, something that was omitted from previous PHP releases. Any constants that are written within a trait will be visible in the scope of classes that use it.

This example demonstrates the possibilities for accessing a trait’s constants, within the trait’s code, the code of the class that uses it, and from the global scope:

Note that you cannot access the constant’s value directly on the trait, from the global scope. The following code will throw a “cannot access trait constant directly” error:

A Modern Object-Oriented Approach to Random Numbers

PHP 8.2 adds a new object-oriented random number generation extension. It allows you to produce random numbers using several modern generation engines. This example demonstrates how to produce a random integer between 1 and 100 with the xoshiro256** engine:

If you subsequently want to switch to a different engine, you need only replace the parameter that’s passed to your Randomizer instance:

Prevent Passwords Leaking Into Stack Traces and Error Logs

Code like the following is a standard feature in many PHP codebases:

This poses a challenge when the function throws an uncaught exception. PHP’s stack traces include the values of function parameters, so the password ends up being emitted to your error logs. This is a security risk.

PHP 8.2 addresses the problem by providing a new attribute that marks parameters as “sensitive.” Applying the #[\SensitiveParameter] attribute to any parameter will redact its value from stack traces:

The modified stack trace will look similar to the following:

It’ll be worthwhile auditing your codebase to find parameters with sensitive values after you upgrade. Add the attribute to any instances you find. This will help prevent leaked logs from compromising your environment’s security.

Dynamic Class Properties Have Been Deprecated

PHP has historically allowed you to set properties on object instances without first declaring them:

This behavior is often problematic. Neither you nor automated static analysis tools can assert which properties the instances will have.

Dynamic properties also facilitate typos which can be difficult to spot:

You’ve mistakenly set the Usernamee property, but PHP won’t throw an error or provide any help. This causes you to spend time debugging why the correct Username property doesn’t have the value you expect.

PHP 8.2 addresses these problems by deprecating dynamic properties. Going forwards, you should define all properties a class can accept, either individually or as promoted constructor properties.

As this is a deprecation and not a removal, existing code will continue to function for now. A deprecation notice will be logged each time a dynamic property is read or set. The removal, which could occur in PHP 9.0, will be a breaking change. Any code that relies on dynamic properties will stop working.

There is a way to continue using dynamic properties, however. You can explicitly opt-in a class to dynamic properties by marking it with the new #[\AllowDynamicProperties] attribute. This will continue to work in PHP 9.0 too. It solves some of the legitimate use cases for dynamic properties, such as config stores, and temporary maps and caches.

Use of this attribute should be discouraged except in cases similar to this example, where the class is dynamic by nature. The deprecation of dynamic properties is intended to help you write safer code that’s less vulnerable to mistakes.

This change does not affect classes that use the __get() and __set() magic methods. They’ll continue to work as normal, allowing you to implement your own routines when an undefined property is read or set. Instances of \stdClass() also continue to support dynamic properties.

Summary

PHP 8.2 is an exciting new release of usability improvements. It includes time-saving readonly classes, more flexible type definitions, and small adjustments that enhance the developer experience, such as constants in traits, enum values in constant expressions, and sensitive parameter value redaction for stack traces.

The upgrade is available now through all supported PHP distribution channels. Moving to 8.2 should be straightforward for most modern codebases that are already written using PHP 8.x features and standards. There are some narrow backward compatibility breaks to be aware of, however, so refer to the official migration guide to learn about all the changes and what you must do to prepare.