PHP 8.2: What's new?
Software Development
Sebastian Luczak
Sebastian Luczak
PHP Unit Leader
2022-08-31

PHP 8.2: What's new?

The new version of PHP is just around the corner. What are the new implementations you should know about? Check this article to find out!

PHP 8.2 is about to be released. Perhaps it will be the version that makes an upgrade into PHP 8 seem appealing to everyone. Let’s talk about what developers can look forward to with PHP 8.2 and prepare for its latest version. But first, let’s quickly go through all the PHP versions and changes over the years.

Overview of past versions

PHP 7.4

Typed properties

<?php
class User {
    public int $id;
    public string $name;
}
?>

Arrow functions

<?php
$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3, 4]);
// $nums = array(10, 20, 30, 40);
?>

Limited return type covariance and argument type contravariance

<?php
class A {}
class B extends A {}

class Producer {
    public function method(): A {}
}
class ChildProducer extends Producer {
    public function method(): B {}
}
?>

Null coalescing assignment operator

<?php
$array['key'] ??= computeDefault();
// is roughly equivalent to
if (!isset($array['key'])) {
    $array['key'] = computeDefault();
}
?>

Unpacking inside arrays

<?php
$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];
?>

Custom object serialization

<?php
// Returns array containing all the necessary state of the object.
public function __serialize(): array;

// Restores the object state from the given data array.
public function __unserialize(array $data): void;
?>

PHP 8.0

Released in November 2020, PHP 8.0 brought us the best features yet, which includes:

Named arguments

htmlspecialchars($string, double_encode: false);

Attributes

class PostsController
{
    #[Route("/api/posts/{id}", methods: ["GET"])]
    public function get($id) { /* ... */ }
}

Constructor property promotion

class Point {
  public function __construct(
    public float $x = 0.0,
    public float $y = 0.0,
    public float $z = 0.0,
  ) {}
}

Union types

class Number {
  public function __construct(
    private int|float $number
  ) {}
}

new Number('NaN'); // TypeError

Match expression

echo match (8.0) {
  '8.0' => "Oh no!",
  8.0 => "This is what I expected",
};
//> This is what I expected

Nullsafe operator

$country = $session?->user?->getAddress()?->country;

PHP 8.1

Enumerations

enum Status
{
    case Draft;
    case Published;
    case Archived;
}
function acceptStatus(Status $status) {...}

Readonly Properties

class BlogData
{
    public readonly Status $status;
  
    public function __construct(Status $status)
    {
        $this->status = $status;
    }
}

First-class Callable Syntax

$foo = $this->foo(...);

$fn = strlen(...);

New in initializers

class Service
{
    private Logger $logger;
   
    public function __construct(
        Logger $logger = new NullLogger(),
    ) {
        $this->logger = $logger;
    }
}

Pure Intersection Types

function count_and_iterate(Iterator&Countable $value) {
    foreach ($value as $val) {
        echo $val;
    }

    count($value);
}

Fibers

$response = $httpClient->request('https://example.com/');
print json_decode($response->getBody()->buffer())['code'];

PHP 8.2

PHP 8.2 introduces further changes aimed at making the developer's life easier and optimizing his work even more. Below is the list of new features.

Readonly classes

One of the biggest improvements in the new version of PHP is the ability to directly create a readonly class. A class described with this feature will automatically propagate it for its variables. DTO classes will now look neat and clean!

readonly class InvoiceDTO
{
    public function __construct(
        public UUID $uuid, 
        public Issuer $issuer,
        public DateTime $issuedAt,
    ) {}
}

Deprecate dynamic properties

The second huge change is the deprecation of dynamic variables in classes. The following implementation will throw a deprecation in PHP 8.2 and ErrorException in future version of PHP.

class MyUser
{
    public string $name;
}
(...)
$myUser->name = 'Name'; // OK
$myUser->surname = 'Surname'; // deprecated / errorexception

It is worth mentioning that classes implementing __get and __set methods and classes directly inheriting from stdClass can still implement magic methods without any obstacles.

Here I also refer you to an interesting thread on GitHub, where PHP-CS developers discuss this change and the need to modify their popular tool for the new version of the language.

Last but not least, you can disable this behavior via Annotation.

#[AllowDynamicProperties]
class MyUser
{
    public string $name;
}

$myUser->surname = 'Surname'; // OK

New standalone types: null, true, and false

Until now, functions that always returned a true or false value had to be described with a bool type.

function alwaysTrue(): bool { return true; }

From now on, we can use true and false as simple types in the returned values of functions.

function alwaysTrue(): true { return true; }

Disjunctive Normal Form Types

(DNF) is a standard way of organizing boolean expressions. Specifically, it means structuring a boolean expression into an ORed series of ANDs. When applied to type declarations, it allows for a standard way to write combined Union and Intersection types that the parser can handle.

It's a big change, as we now can have nullable intersection types, for example:

function getFullName((HasName&HasSurname)|null $user) { ... }

Constants in Traits

I'm not a big proponent of using Traits and such a change is purely cosmetic to me, especially since it doesn't allow you to use Trait values without initializing the object.

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';
        }
    }
}

Redacting parameters in back traces

One of the most important changes I am looking forward to. In the latest version of PHP we will be able to mark variables as SensitiveParameterValue. Why should we?

PHP's stack traces in exceptions are very useful for debugging, however, they allow you to preview parameter values. For example, let's imagine PDO code used to connect to a database. The debug trace would look as follows:

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}

After using Annotation #[SensitiveParameter] our stack trace will no longer show the value of the variable.

function test(
    $foo,
    #[\SensitiveParameter] $bar,
    $baz
) {
    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
*/

Fetch properties of enums in const expressions

As author says , the primary motivation for this change is to allow fetching the name and value properties in places where enum objects aren't allowed, like array keys. We could work on arrays so they could be extended to allow enums or all objects as keys, but allowing to fetch properties of enums is simpler.

enum A: string {
    case B = 'B';
    const C = [self::B->value => self::B];
}

Date functions return types

Previously static methods worked like this:

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

In PHP 8.2 it's going to be changed to:

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

This is a breaking change for library creators and/or all custom implementations of DateTime.

Deprecate and Remove utf8_encode and utf8_decode

Those were two function that did not served it purpose, as they only converted between ISO-8859-1 and UTF-8. PHP Manual suggest using mb_convert_encoding instead.

Locale-independent case conversion

Locale sensitivity is best described by author of the RFC:

Prior to PHP 8.0, PHP's locale was set from the environment. When a user installs Linux, it asks what language you want it to be in. The user might not fully appreciate the consequences of this decision. It not only sets the user interface language for built-in commands, it also pervasively changes how string handling in the C library works. For example, a user selecting “Turkish” when installing Linux would find that applications calling toupper('i') would obtain the dotted capital I (U+0130, “İ”).

In an era of standardized text-based protocols, natural language is a minority application for case conversion. But even if the user did want natural language case conversion, they would be unlikely to achieve success with strtolower(). This is because it processes the string one byte at a time, feeding each byte to the C library's tolower(). If the input is UTF-8, by far the most popular modern choice, strtolower() will mangle the string, typically producing invalid UTF-8 as output.

PHP 8.0 stopped respecting the locale environment variables. So the locale is always “C” unless the user explicitly calls setlocale(). This means that the bulk of the backwards-incompatible change is already behind us. Any applications depending on the system locale to do case conversion of legacy 8-bit character sets would have been broken by PHP 8.0.

What it means is that all of below function will do ASCII case conversion from PHP.8.2: strtolower, strtoupper, stristr, stripos, strripos, lcfirst, ucfirst, ucwords, str_ireplace

Deprecate ${} string interpolation

We've got a lot of ways of embedding variables into strings in PHP:

  • Directly embedding variables (“$foo”)
  • Braces outside the variable (“{$foo}”)
  • Braces after the dollar sign (“${foo}”)
  • Variable variables (“${expr}”, equivalent to (string) ${expr})

To avoid confusion and misuse those will not work anymore:

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

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

Summary

These are not all the changes that PHP 8.2 will offer us. Unfortunately, we still didn't get support for generic types, according to what Nikita said the monomorphized generics would add too much performance overhead, and reified generics require many changes across the whole codebase. What is noticeable, however, is the discipline and vision of the product. The changes introduced in successive versions of the language are becoming clearer, and those interested will notice that PHP is moving in the right direction both in the area of syntax simplification and support for novelties. I expect that as early as next year we will see callable as a valid type.

PHP development free consulting

Read more:

5 Mistakes You Should Avoid While Maintaining a Project in PHP

PHP Development. Symfony Console Component - Tips & Tricks

Why do we need Symfony Polyfill (... and why we shouldn't)