What's new in PHP 8.2

Written by / Original link on Jun. 17, 2022

PHP 8.2 will be released on November 24, 2022. In this post, we'll go through all features, performance improvements, changes and deprecations one by one.

# null and false as standalone types RFC

We'll avoid going down the rabbit hole of type safety in this post, but technically null and false could be considered valid types on their own. Common examples are PHP's built-in functions, where false is used as the return type for when an error occurs. For example in file_get_contents:

file_get_contents(/* … */): string|false

Note that using false in a union type was already possible; but in PHP 8.2, it can be used as a standalone type as well:

function alwaysFalse(): false
    return false;

Many developers, including myself, are a bit wary looking at this RFC. It doesn't support true as a standalone type, and shouldn't types represent categories instead of individual values? It turns out there is a concept called a unit type in type systems, which are types that only allow one value. But is it really useful, and does it encourage clean software design? Maybe, maybe not.

A standalone null type makes a bit more sense: as null can be considered a category on itself and not just a value within a category. Imagine the null object pattern:

class Post 
    public function getAuthor(): ?string { /* … */ }

class NullPost extends Post
    public function getAuthor(): null { /* … */ }

It makes sense for NullPost::getAuthor() to be able to say it will only ever return null, instead of null or string, which previously wasn't possible to do.

Personally, I'd stay away from using false as a standalone type for conveying error state — I think there are better solutions to solving such problems. I can see some use cases for null as a standalone type and I'll probably use it sporadically.

# Readonly classes RFC

Readonly properties were introduced in PHP 8.1. This RFC builds on top of them, and adds syntactic sugar to make all class properties readonly at once. Instead of writing this:

class Post
    public function __construct(
        public readonly string $title, 
        public readonly Author $author,
        public readonly string $body,
        public readonly DateTime $publishedAt,
    ) {}

You can now write this:

readonly class Post
    public function __construct(
        public string $title, 
        public Author $author,
        public string $body,
        public DateTime $publishedAt,
    ) {}

Functionally, making a class readonly is entirely the same as making every property readonly; but it will also prevent dynamic properties being added on a class:

$post = new Post(/* … */);

$post->unknown = 'wrong';

Uncaught Error: Cannot create dynamic property Post::$unknown

# Deprecate dynamic properties RFC

Speaking of dynamic properties: I'd say this is a change for the better, but it will hurt a little bit. Dynamic properties are deprecated in PHP 8.2, and will throw an ErrorException in PHP 9.0. What are dynamic properties, you ask? Those are properties that aren't present on an object, but are set or get nevertheless:

class Post
    public string $title;

// …

$post->name = 'Name';

Keep in mind that classes implementing __get and __set will still work as intended:

class Post
    private array $properties = [];
    public function __set(string $name, mixed $value): void
        $this->properties[$name] = $value;

// …

$post->name = 'Name';

The same goes for objects of stdClass, they will keep supporting dynamic properties.

PHP used to be a very dynamic language, but has been moving away from that mindset for a while now. Personally I think it's a good thing to embrace stricter rules and rely on static analysis wherever possible, as I think it allows developers to write better code.

Still, I can imagine developers who relied on dynamic properties not being happy with this change; maybe it would help to take a deeper look into the benefits of static analysis? You can check out my Road to PHP: Static Analysis series if you want to learn more!

And just in case you don't want these warnings when upgrading to PHP 8.2, there's a couple things you can do.

You can use the #[AllowDynamicProperties] attribute on classes that should still allow those properties:

class Post
    public string $title;

// …

$post->name = 'Name'; // All fine

Another option is to simply disable deprecation warnings. I wouldn't recommend doing that, since you'll get in trouble with PHP 9.0, but here's how you can disable deprecation warnings in PHP anyway:

error_reporting(E_ALL ^ E_DEPRECATED);

How I set up PhpStorm to be clean and minimal in less than 5 minutes.


Clean and minimalistic PhpStorm

# Redact parameters in back traces RFC

A common practice in any codebase is to send production errors to a service that keeps track of them, and will notify developers when something goes wrong. This practice often involves sending stack traces over the wire to a third party service. There are case however where those stack traces can include sensitive information such as environment variables, passwords or usernames.

PHP 8.2 allows you to mark such "sensitive parameters" with an attribute, so that you don't need to worry about them being listed in your stack traces when something goes wrong. Here's an example from the RFC:

function test(
    #[\SensitiveParameter] $bar,
) {
    throw new Exception('Error');
test('foo', 'bar', 'baz');
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('foo', Object(SensitiveParameterValue), 'baz')
#1 {main}
  thrown in test.php on line 8

# Return type changes for DateTime::createFromImmutable() and DateTimeImmutable::createFromMutable() breaking

Previously, these methods looked like this:

DateTime::createFromImmutable(): DateTime
DateTimeImmutable::createFromMutable(): DateTimeImmutable

In PHP 8.2 those method signatures are changed like so:

DateTime::createFromImmutable(): static
DateTimeImmutable::createFromMutable(): static

This change makes a lot more sense, as it improves static insight possibilities for classes extending from DateTime and DateTimeImmutable. However, technically, this is a breaking change that might affect custom implementations that extend from either of those two classes.

# utf8_encode() and utf8_decode() deprecations RFC

In PHP 8.2, using either utf8_encode() or utf8_decode() will trigger these deprecation notices:

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

The RFC argues that these functions have a inaccurate name that often causes confusion: these functions only convert between ISO-8859-1 and UTF-8, while the function name suggest a more broader use. There's a more detailed explanation about the reasoning in the RFC.

The alternative? The RFC suggests using mb_convert_encoding() instead.

# Locale-insensitive strtolower() and strtoupper() breaking RFC

Both strtolower() and strtoupper() are no longer locale-sensitive. You can use mb_strtolower() if you want localized case conversion.

# Signature changes to several SPL methods breaking

Several methods of SPL classes have been changed to properly enforce their correct type signature:


# New n modifier in PCRE upgrading

You can now use the n modifier (NO_AUTO_CAPTURE) in pcre* functions.

# ODBC username and password escaping breaking

From the UPGRADING guide:

The ODBC extension now escapes the username and password for the case when both a connection string and username/password are passed, and the string must be appended to

The same applies to PDO_ODBC.

# Deprecate ${} string interpolation RFC

PHP has several ways of embedding variables in strings. This RFC deprecates two ways of doing so, since they are rarely used, and often lead to confusion:

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

To be clear: the two popular ways of string interpolation still work:

"Hello {$world}";
"Hello $world";

# Deprecate partially supported callables RFC

Another change, although one with a slightly smaller impact, is that partially supported callables are now deprecated as well. Partially supported callables are callables which can be called using call_user_func($callable), but not by calling $callable() directly. The list of these kinds of callables is rather short, by the way:

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

The reason for doing this? It's a step in the right direction towards being able to use callable for typed properties. Nikita explains it very well in the RFC:

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.

That's all there is for now, I'll keep this list updated throughout the year. You can subscribe to my newsletter if you want to receive occasional updates!

Noticed a tpyo? You can submit a PR to fix it. If you want to stay up to date about what's happening on this blog, you can follow me on Twitter or subscribe to my newsletter: Email Subscribe

« Dealing with deprecations - Dynamic Strategies »