PHP 8.2 builds upon the renewed base set forth by PHP 8.0 and PHP 8.1. Now that PHP 8.2 has been released, let’s cover what’s new in PHP 8.2 in detail — from its new features and improvements to deprecations and minor changes, we’ll go through them all.

As PHP 8.2 entered its feature freeze on July 19, 2022, you can expect no significant additions to this list.

Excited? We’re too.

Let’s begin!

New Features and Improvements in PHP 8.2

Let’s start by exploring all the latest PHP 8.2 features. It’s quite an extensive list:

New readonly Classes

PHP 8.1 introduced the readonly feature for class properties. Now, PHP 8.2 is adding support to declare the entire class as readonly.

If you declare a class as readonly, all its properties will automatically inherit the readonly feature. Thus, declaring a class readonly is the same as declaring every class property as readonly.

For example, with PHP 8.1, you had to write this tedious code to declare all class properties as readonly:

class MyClass
{
public readonly string $myValue,
public readonly int $myOtherValue
public readonly string $myAnotherValue
public readonly int $myYetAnotherValue
}

Imagine the same with many more properties. Now, with PHP 8.2, you can just write this:

readonly class MyClass
{
public string $myValue,
public int $myOtherValue
public string $myAnotherValue
public int $myYetAnotherValue
}

You can also declare abstract or final classes as readonly. Here, the order of the keywords doesn’t matter.

abstract readonly class Free {}
final readonly class Dom {}

You can also declare a readonly class with no properties. Effectively, this prevents dynamic properties while still allowing child classes to declare their readonly properties explicitly.

Next up, readonly classes can only contain typed properties — the same rule for declaring individual readonly properties.

You can use the mixed type property if you cannot declare a strictly typed property.

Trying to declare a readonly class without a typed property will result in a Fatal error:

readonly class Type {
    public $nope;
}
Fatal error: Readonly property Type::$nope must have type in ... on line ... 

Furthermore, you cannot declare readonly for certain PHP features:

Attempting to declare any of these features as readonly will result in a Parse error.

readonly interface Destiny {}
Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in ... on line ...

As is the case for all PHP keywords, the readonly keyword is case insensitive.

PHP 8.2 also deprecates dynamic properties (more on that later). But you cannot prevent dynamic properties from being added to a class. However, doing so for a readonly class will only result in a Fatal Error.

Fatal error: Readonly property Test::$test must have type in ... on line ...

Allow truefalse, and null as Standalone Types

PHP already includes scalar types like int, string, and bool. That was expanded in PHP 8.0 with the addition of union types, allowing values to be of different types. The same RFC also allowed using false and null as part of a union type — they weren’t allowed as standalone types, though.

If you tried declaring false or null or as standalone types — without them being part of a union type — it resulted in a fatal error.

function spam(): null {}
function eggs(): false {}

Fatal error: Null can not be used as a standalone type in ... on line ...
Fatal error: False can not be used as a standalone type in ... on line ...

To avoid this scenario, PHP 8.2 is adding support for using false and null as standalone types. With this addition, PHP’s type system is more expressive and complete. You can now declare the return, parameter, and property types precisely.

Also, PHP still doesn’t include a true type, which seems to be a natural counterpart of the false type. PHP 8.2 fixes that and adds support for the true type as well. It doesn’t allow coercion, exactly like how the false type behaves.

Both true and false types are essentially a union type of PHP’s bool type. To avoid redundancy, you cannot declare these three types together in a union type. Doing so will result in a compile-time fatal error.

Disjunctive Normal Form (DNF) Types

Disjunctive Normal Form (DNF) is a standardized way of organizing boolean expressions. It consists of a disjunction of conjunctions — in boolean terms, that’s an OR of ANDs.

Applying DNF to type declarations allows for a standard way to write combined Union and Intersection types that the parser can handle. PHP 8.2’s new DNF types feature is simple yet powerful if used properly.

The RFC gives the following example. It assumes the following interface and class definitions already exist:

interface A {}
interface B {}
interface C extends A {}
interface D {}

class W implements A {}
class X implements B {}
class Y implements A, B {}
class Z extends Y implements C {}

With DNF types, you can perform type declarations for properties, parameters, and return values like so:

// Accepts an object that implements both A and B,
// OR an object that implements D
(A&B)|D

// Accepts an object that implements C, 
// OR a child of X that also implements D,
// OR null
C|(X&D)|null

// Accepts an object that implements all three of A, B, and D, 
// OR an int, 
// OR null.
(A&B&D)|int|null

In some cases, the properties may not be in DNF forms. Declaring them as such will result in a parse error. But you can always rewrite them as:

A&(B|D)
// Can be rewritten as (A&B)|(A&D)

A|(B&(D|W)|null)
// Can be rewritten as A|(B&D)|(B&W)|null

You should note that each segment of a DNF type must be unique. For instance, declaring (A&B)|(B&A) is invalid as the two ORed segments are logically the same.

Adding to this, segments that are strict subsets of the other segment aren’t allowed either. That’s because the superset will already have all instances of the subset, making it redundant to use DNF.

Redact Sensitive Parameters in Back Traces

Like almost any programming language, PHP allows tracing its call stack at any point in the code’s execution. Stack tracing makes it easy to debug code to fix errors and performance bottlenecks. It forms the backbone of tools like Kinsta APM, our custom-designed performance monitoring tool for WordPress sites.

Tracking slow WooCommerce transactions through the Kinsta APM tool.
Tracking slow WooCommerce transactions with Kinsta APM.

Performing a stack trace doesn’t halt the program’s execution. Typically, most stack traces run in the background and are logged silently — for later inspection if needed.

However, some of these detailed PHP stack traces can be a drawback if you share them with third-party services — usually for error log analysis, error tracking, etc. These stack traces may include sensitive information such as usernames, passwords, and environment variables.

This RFC proposal gives one such example:

One common “offender” is PDO which takes the database password as a constructor parameter and immediately attempts to connect to the database within the constructor, instead of having a pure constructor and a separate ->connect() method. Thus when the database connection fails the stack trace will include the database password:

PDOException: SQLSTATE[HY000] [2002] No such file or directory in /var/www/html/test.php:3
Stack trace: #0 /var/www/html/test.php(3): PDO->__construct('mysql:host=loca...', 'root', 'password')
#1 {main}

PHP 8.2 allows you to mark such sensitive parameters with a new \SensitiveParameter attribute. Any parameter marked sensitive will not be listed in your backtraces. Thus, you can share them without concerns with any third-party services.

Here’s a straightforward example with a single sensitive parameter:

<?php

function example(
    $ham,
    #[\SensitiveParameter] $eggs,
    $butter
) {
    throw new \Exception('Error');
}

example('ham', 'eggs', 'butter');

/*
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('ham', Object(SensitiveParameterValue), 'butter')
#1 {main}
thrown in test.php on line 8
*/

When you generate a backtrace, any parameter with the \SensitiveParameter attribute will be replaced with a \SensitiveParameterValue object, and its real value will never be stored in the trace. The SensitiveParameterValue object encapsulates the actual parameter value — if you need it for any reason.

New mysqli_execute_query Function and mysqli::execute_query Method

Have you ever used the mysqli_query() function with dangerously escaping user values just to run a parameterized MySQLi query?

PHP 8.2 makes running parameterized MySQLi queries easier with the new mysqli_execute_query($sql, $params) function and mysqli::execute_query method.

Essentially, this new function is a combination of mysqli_prepare(), mysqli_execute(), and mysqli_stmt_get_result() functions. With it, the MySQLi query will be prepared, bound (if you pass any parameters), and executed within the function itself. If the query runs successfully, it’ll return a mysqli_result object. If unsuccessful, it’ll return false.

The RFC proposal gives a simple yet powerful example:

foreach ($db->execute_query('SELECT * FROM user WHERE name LIKE ? AND type_id IN (?, ?)', [$name, $type1, $type2]) as $row) {
print_r($row);
}

Fetch enum Properties in const Expressions

This RFC proposes allowing the ->/?-> operator to fetch enum properties in const expressions.

The main reason for this new feature is that you cannot use enum objects in some places, like array keys. In such a case, you’ll have to repeat the value of the enum case just to use it.

Allowing fetching of enum properties in places where enum objects aren’t allowed can simplify this procedure.

It means the following code is now valid:

const C = [self::B->value => self::B];

And just to be safe, this RFC also includes support for the nullsafe operator ?->.

Allow Constants in Traits

PHP includes a way to reuse code called Traits. They’re great for code reuse across classes.

Currently, Traits only allow defining methods and properties, but not constants. That means you cannot define invariants expected by a Trait within the Trait itself. To get around this limitation, you need to define constants in its composing class or an interface implemented by its composing class.

This RFC proposes to allow defining constants in Traits. These constants can be defined just like you’d define class constants. This example taken straight from the RFC clears the air around its usage:

trait Foo {
    public const FLAG_1 = 1;
    protected const FLAG_2 = 2;
    private const FLAG_3 = 2;

    public function doFoo(int $flags): void {
        if ($flags & self::FLAG_1) {
            echo 'Got flag 1';
        }
        if ($flags & self::FLAG_2) {
            echo 'Got flag 2';
        }
        if ($flags & self::FLAG_3) {
        echo 'Got flag 3';
        }
    }
}

Trait constants are also merged into the composing class’ definition, the same as a Trait’s property and method definitions. They also have similar restrictions as properties of Traits. As noted in the RFC, this proposal — though a good start — needs further work to flesh out the feature.

Deprecations in PHP 8.2

We can now move to explore all the deprecations in PHP 8.2. This list isn’t quite as big as its new features:

Deprecate Dynamic Properties (and New #[AllowDynamicProperties] Attribute)

Up until PHP 8.1, you could dynamically set and retrieve undeclared class properties in PHP. For example:

class Post {
    private int $pid;
}

$post = new Post();
$post->name = 'Kinsta';

Here, the Post class doesn’t declare a name property. But because PHP allows dynamic properties, you can set it outside the class declaration. That’s its biggest — and possibly, the only — advantage.

Dynamic properties allow unexpected bugs and behavior to crop up in your code. For instance, if you make any mistake while declaring a class property outside of the class, it’s easy to lose track of it — especially when debugging any errors within that class.

From PHP 8.2 onwards, dynamic properties are deprecated. Setting a value to an undeclared class property will emit a deprecation notice the first time the property is set.

class Foo {}
$foo = new Foo;

// Deprecated: Creation of dynamic property Foo::$bar is deprecated
$foo->bar = 1;

// No deprecation warning: Dynamic property already exists.
$foo->bar = 2;

However, from PHP 9.0 onwards, setting the same will throw an ErrorException error.

If your code is full of dynamic properties — and there’s a lot of PHP code that is — and if you want to stop these deprecation notices after upgrading to PHP 8.2, you can use PHP 8.2’s new #[AllowDynamicProperties] attribute to allow dynamic properties on classes.

#[AllowDynamicProperties]
class Pets {}
class Cats extends Pets {}

// You'll get no deprecation warning
$obj = new Pets;
$obj->test = 1;

// You'll get no deprecation warning for child classes
$obj = new Cats;
$obj->test = 1;

As per the RFC, classes marked as #[AllowDynamicProperties], as well as their child classes, can continue using dynamic properties without deprecation or removal.

You should also note that, in PHP 8.2, the only bundled class marked as #[AllowDynamicProperties] is stdClass. Furthermore, any properties accessed through __get() or __set() PHP magic methods are not considered dynamic properties, so that they won’t throw a deprecation notice.

Deprecate Partially Supported Callables

Another PHP 8.2 change, albeit with a more negligible impact, is to deprecate partially supported callables.

These callables are termed partially supported because you cannot interact with them directly via $callable(). You can only get to them with the call_user_func($callable) function. The list of such callables is not long:

"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]

From PHP 8.2 onwards, any attempts to invoke such callables — such as via call_user_func() or array_map() functions — will throw a deprecation warning.

The original RFC gives solid reasoning behind this deprecation:

Apart from the last two cases, all of these callables are context-dependent. The method that "self::method" refers to depends on which class the call or callability check is performed from. In practice, this usually also holds for the last two cases, when used in the form of [new Foo, "parent::method"].

Reducing the context-dependence of callables is the secondary goal of this RFC. After this RFC, the only scope-dependence still left is method visibility: "Foo::bar" may be visible in one scope, but not another. If callables were to be limited to public methods in the future (while private methods would have to use first-class callables or Closure::fromCallable() to be made scope-independent), then the callable type would become well-defined and could be used as a property type. However, changes to visibility handling are not proposed as part of this RFC.

As per the original RFC, the is_callable() function and the callable type will continue to accept these callables as exceptions. But only until support for them is removed entirely from PHP 9.0 onwards.

To avoid confusion, this deprecation notice scope was expanded with a new RFC — it now includes these exceptions.

It’s good to see PHP moving towards having a well-defined callable type.

Deprecate #utf8_encode() and utf8_decode() Functions

PHP’s built-in functions utf8_encode() and utf8_decode() convert strings encoded in ISO-8859-1 (“Latin 1”) to and from UTF-8.

However, their names suggest a more general use than their implementation allows. The “Latin 1” encoding is commonly confused with other encodings like the “Windows Code Page 1252.”

Furthermore, you’ll usually see Mojibake when these functions cannot convert any string properly. The lack of error messages also means it’s difficult to spot them, especially within a sea of otherwise legible text.

PHP 8.2 deprecates both #utf8_encode() and utf8_decode() functions. If you invoke them, you’ll see these deprecation notices:

Deprecated: Function utf8_encode() is deprecated
Deprecated: Function utf8_decode() is deprecated

The RFC suggests using PHP’s supported extensions like mbstringiconv, and intl instead.

Deprecate ${} String Interpolation

PHP allows embedding variables in strings with double-quotes (") and heredoc (<<<) in several ways:

  1. Directly embedding variables — “$foo”
  2. With braces outside the variable — “{$foo}”
  3. With braces after the dollar sign — “${foo}”
  4. Variable variables — “${expr}” — equivalent to using (string) ${expr}

The first two ways have their pros and cons, while the latter two have complex and conflicting syntax. PHP 8.2 deprecates the last two ways of string interpolation.

You should steer clear of interpolating strings this way going forward:

"Hello, ${world}!";
Deprecated: Using ${} in strings is deprecated

"Hello, ${(world)}!";
Deprecated: Using ${} (variable variables) in strings is deprecated

Starting with PHP 9.0, these deprecations will be upgraded to throw an exception error.

Deprecate mbstring Functions for Base64/QPrint/Uuencode/HTML Entities

PHP’s mbstring (multi-byte string) functions help us work with Unicode, HTML entities, and other legacy text encodings.

However, Base64, Uuencode, and QPrint aren’t text encodings and are still a part of these functions — primarily due to legacy reasons. PHP also includes separate implementations of these encodings.

As for HTML entities, PHP has built-in functions — htmlspecialchars() and htmlentities() — to deal with these better. For example, unlike with mbstring, these functions will also convert <. >, and & characters to HTML entities.

Moreover, PHP is always improving its built-in functions — just like PHP 8.1 with HTML encoding and decoding functions.

So, keeping all that in mind, PHP 8.2 is deprecating the use of mbstring for these encodings (the labels are case-insensitive):

  • BASE64
  • UUENCODE
  • HTML-ENTITIES
  • html (alias of HTML-ENTITIES)
  • Quoted-Printable
  • qprint (alias of Quoted-Printable)

From PHP 8.2 onwards, using mbstring to encode/decode any of the above will emit a deprecation notice. PHP 9.0 will remove mbstring support for these encodings altogether.

Other Minor Changes in PHP 8.2

Finally, we can discuss PHP 8.2’s minor changes, including its removed features and functionalities.

Remove Support for libmysql from mysqli

As of now, PHP allows both mysqli and PDO_mysql drivers to build against mysqlnd and libmysql libraries. However, the default and recommended driver since PHP 5.4 has been mysqlnd.

Both these drivers have many advantages and disadvantages. However, removing support for one of them — ideally, removing libmysql as it’s not the default — will simplify PHP’s code and unit tests.

To make an argument for this favor, the RFC lists down many advantages of mysqlnd:

  • It’s bundled with PHP
  • It uses PHP memory management to monitor memory usage and
    improve performance
  • Provides quality-of-life functions (e.g. get_result())
  • Returns numeric values using PHP native types
  • Its functionality does not depend on the external library
  • Optional plugin functionality
  • Supports asynchronous queries

The RFC also lists some advantages of libmysql, including:

  • Auto-reconnect is possible ( mysqlnd doesn’t support this functionality intentionally because it can be easily exploited)
  • LDAP and SASL authentication modes (mysqlnd may add this feature soon too)

In addition, the RFC lists many disadvantages of libmysql — incompatibility with the PHP memory model, many failing tests, memory leaks, differing functionalities between versions, etc.

Keeping all this in mind, PHP 8.2 removed support for building mysqli against libmysql.

If you want to add any functionality that’s only available with libmysql, you’ll have to add it explicitly to mysqlnd as a feature request. Also, you cannot add auto-reconnect.

Locale-Independent Case Conversion

Before PHP 8.0, PHP’s locale was inherited from the system environment. But this could cause a problem in some edge cases.

Setting your language while installing Linux will set the appropriate user interface language for its built-in commands. However, it also unexpectedly changes how the C library’s string handling functionality works.

For instance, if you selected “Turkish” or “Kazakh” language when installing Linux, you’ll find that calling toupper('i') to get its uppercase equivalent would obtain the dotted capital I (U+0130, İ).

PHP 8.0 stopped this anomaly by setting the default locale to “C,” unless the user explicitly changes it via setlocale().

PHP 8.2 goes even further by removing locale sensitivity from case conversions. This RFC primarily changes strtolower()strtoupper(), and related functions. Read the RFC for a list of all the affected functions.

As an alternative, if you want to use localized case conversion, then you can use mb_strtolower().

Random Extension Improvement

PHP is planning to overhaul its random functionality.

As of now, PHP’s random functionality heavily relies on the Mersenne Twister state. However, this state is implicitly stored in PHP’s global area — there’s no way a user can access it. Adding randomization functions between the initial seeding stage and the intended usage would break the code.

Maintaining such code can be even more complicated when your code uses external packages.

Thus, PHP’s current random functionality cannot reproduce random values consistently. It even fails empirical statistical tests of uniform random number generators, like TestU01’s Crush and BigCrush. Mersenne Twister’s 32-bit limitation further exacerbates that.

Thus, using PHP’s built-in functions — shuffle(), str_shuffle(), array_rand() — is not recommended if you need cryptographically secure random numbers. In such cases, you’ll need to implement a new function using random_int() or similar functions.

However, several issues with this RFC were raised after the voting had begun. This setback forced the PHP team to note all the issues in a separate RFC, with a ballot option created for each issue. They’ll decide on moving further only after reaching a consensus.

Additional RFCs in PHP 8.2

PHP 8.2 also includes many new functions and minor changes. We’ll mention them below with links to additional resources:

  1. New curl_upkeep function: PHP 8.2 adds this new function to its Curl extension. It calls the curl_easy_upkeep() function in libcurl, the underlying C library that the PHP Curl extension uses.
  2. New ini_parse_quantity function: PHP INI directives accept data sizes with a multiplier suffix. For instance, you can write 25 Megabytes as 25M, or 42 Gigabytes as just 42G. These suffixes are common in PHP INI files but are uncommon elsewhere. This new function parses the PHP INI values and returns their data size in bytes.
  3. New memory_reset_peak_usage function: This function resets the peak memory usage returned by the memory_get_peak_usage function. It can be handy when you’re running the same action multiple times and want to record each run’s peak memory usage.
  4. Support for no-capture modifier (/n) in preg_* functions: In regex, the () metacharacters indicate a capturing group. That means all matches for the expression within the bracket are returned. PHP 8.2 adds a  no-capture modifier (/n) to stop this behavior.
  5. Make the iterator_*() family accept all iterables: As of now, PHP’s iterator_*() family only accepts \Traversables (i.e. no plain arrays allowed). It’s unnecessarily limiting, and this RFC fixes that.

Summary

PHP 8.2 builds upon the massive improvements in PHP 8.0 and PHP 8.1, which is no easy feat. We think the most exciting PHP 8.2 features are its new standalone types, readonly properties, and numerous performance improvements.

We can’t wait to benchmark PHP 8.2 with various PHP frameworks and CMSs.

Make sure to bookmark this blog post for your future reference.

Which PHP 8.2 features are your favorite? Which deprecations are your least favorite? Please share your thoughts with our community in the comments!

Salman Ravoof

Salman Ravoof is a self-taught web developer, writer, creator, and a huge admirer of Free and Open Source Software (FOSS). Besides tech, he's excited by science, philosophy, photography, arts, cats, and food. Learn more about him on his website, and connect with Salman on Twitter.