Upcoming features

What's new in PHP 8.3?

Published in PHP on Jun 29, 2023

PHP is not what it used to be. Meanwhile, it is a language with a vibrant community and big (and small) changes that are making developers’ lives increasingly pleasant.

At the end of this year, a number of changes will be added. PHP 8.3 will be released around November and we already know what most of the changes will look like.

To make sure you can start working with the changes right on the release date, here is a preview.

In this post, I will be using examples from the RFCs themselves. If you want to learn more, just click through to the RFC.

Also: I have only included actual new features. Changes to existing functions have not been included in this post.

Implemented

All functions in this section have already been implemented, and are coming to PHP 8.3 for sure.

json_validate

The first one is a new feature that a lot of users have wanted for quite a while now: the function json_validate(string $json).

The entire function signature looks like this:

json_validate(string $json, int $depth = 512, int $flags = 0): bool

It will validate the $json and return true if the entire string is valid JSON, or false if that is not the case. A lot of users already created their own makeshift solutions for this, but those can be discarded now.

The usage of the method is extremely easy:

json_validate('[1, 2, 3]'); // true
json_validate('{1, 2, 3]'); // false

Currently, there is only 1 supported flag: JSON_INVALID_UTF8_IGNORE. It is already accepted by json_decode, and simply makes the method ignore UTF-8 encoded strings.

Arbitrary static variable initializers

You can already declare static variables in functions, which outlive the function call. That looks like this:

function foo() {
    static $i = 1;
    echo $i++, "\n";
}
 
foo();
// 1
foo();
// 2
foo();
// 3

The problem is that you only use constant expressions to initialize the static variable, and not a function or use parameters. This RFC fixes that:

function bar() {
    echo "bar() called\n";
    return 1;
}
 
function foo() {
    static $i = bar();
    echo $i++, "\n";
}
 
foo();
// bar() called
// 1
foo();
// 2
foo();
// 3

Note how the echo is only executed once.

Dynamic class constant fetch

In PHP 8.3 you will be able to use variables to fetch constants from classes and enums. This looks like this:

class MyClass {
    public const MY_CONST = 42;
}

$constName = 'MY_CONST';

echo MyClass::{$constName}; // 42

This will only work for string values of course. Note that you can also use class as a value:

class Foo {}

$constant = 'class';

echo Foo::{$constant}; // "Foo"

mb_str_pad

There is already a function called str_pad which allows you to fill up a string up to a certain number of characters. That looks like this:

var_dump(str_pad('Francais', 10, '_', STR_PAD_RIGHT)); // string(10) "Francais__"

The issue with this, is that it makes mistakes when using multi-byte characters. If you were to actually spell Français the right way, you’d get Français_ as a result, which only has 9 characters instead of 10. This is because the ç is encoded as 2 bytes.

The new function mb_str_pad resolves this, by taking into consideration the number of bytes. The result:

var_dump(mb_str_pad('Français', 10, '_', STR_PAD_RIGHT)); // string(11) "Français__"

The function str_pad won’t be removed.

Accepted

By the time I am writing this, these new features have not actually been implemented yet, but have been accepted for PHP 8.3 already. This is what they will (probably) look like:

Marking overridden methods (#[\Override])

The PHP 8.3 release brings with it a new attribute named #[\Override]. Its main role is to explicitly show the intent to override a method from a parent class or any of the implemented interfaces. When applied, the engine validates whether a method with the same name exists in a parent class or an implemented interface. If such a method is missing, a compile time error will be emitted.

This is an illustration of how to use the #[\Override] attribute:

class ParentClass {
    protected function exampleMethod(): void {}
}
 
class ChildClass extends ParentClass {
    #[\Override]
    public function exampleMethod(): void {}
}

In the above example, the #[\Override] attribute is applied on the exampleMethod of the ChildClass. Since a method with the same name exampleMethod exists in the parent class ParentClass, no error will be thrown.

On the other hand, if the #[\Override] attribute is used on a method which does not exist in any parent class or implemented interfaces, an error will be emitted. This is showcased in the following example:

class SomeClass {
    #[\Override]
    public function nonExistentMethod(): void {} // Fatal error: SomeClass::nonExistentMethod() has #[\Override] attribute, but no matching parent method exists
}

Typed class constants

Until now, the PHP type system did not allow for the declaration of constant types. This has been a minor issue for global constants, but it could lead to confusion and potential bugs when it comes to class constants.

By default, child classes in PHP can override class constants from their parent classes. As a result, it can be nearly impossible to know the type and value of a class constant for sure, unless the defining class or the constant itself is marked as final.

interface I {
    const TEST = "Test";  // We may naively assume that the TEST constant is always a string
}
 
class Foo implements I {
    const TEST = [];      // But it may be an array...
}
 
class Bar extends Foo {
    const TEST = null;    // Or null
}

As seen above, there are instances where the ability to restrict the type of class constants without making them final would be beneficial.

This is where the new feature in PHP 8.3 steps in. The RFC proposes the support for declaring types of class, interface, trait, and enum constants. These are all called class constants in the RFC.

The usage will look like this:

enum E {
    const string TEST = "Test1";   // E::TEST is a string
}
 
trait T {
    const string TEST = E::TEST;   // T::TEST is a string too
}
 
interface I {
    const string TEST = E::TEST;   // I::TEST is a string as well
}
 
class Foo implements I {
    use T;
 
    const string TEST = E::TEST;  // Foo::TEST must also be a string
}
 
class Bar extends Foo {
    const string TEST = "Test2";  // Bar::TEST must also be a string, but the value can change
}

With this feature, you can now declare the type of a constant while still allowing the value of the constant to change in child classes.

That’s it!

For now at least.