Scout APM helps PHP developers pinpoint N+1 queries, memory leaks & more so you can troubleshoot fast & get back to coding faster. Start your free 14-day trial today.

PHP 8.1: new in initializers

PHP 8.1 adds a feature that might seem like a small detail, but one that I think will have a significant day-by-day impact on many people. So what's this "new in initializers RFC" about? Let's take a look at an example; we've all written code like this:

class MyStateMachine
{
    public function __construct(
        private ?State $state = null,
    ) {
        $this->state ??= new InitialState();
    }
}

In this state machine example, we'd like to construct our class in two ways: with and without an initial state. If we construct it without an initial state, we want a default one to be set. PHP of course supports setting initial values directly in the parameter list, but only for primitive types. For example, if our state machine used strings instead of objects internally, we'd be able to write its constructor like so:

class MyStateMachine
{
    public function __construct(
        private string $state = 'initial',
    ) {
    }
}

So with PHP 8.1 we're able to use that same "default value" syntax for objects as well. In other words: you can use new for default arguments (which are one example of "initializers"):

class MyStateMachine
{
    public function __construct(
        private State $state = new InitialState(),
    ) {
    }
}

"Initializers" are more than parameter default values though, here's a simple explanation from the RFC:

This RFC proposes to allow use of new expressions inside parameter default values, attribute arguments, static variable initializers and global constant initializers

You read it right: attributes are in this list as well! Imagine a simple validation library that uses attributes to validate input on properties. Maybe it should be able to validate array elements, something like this:

class CreateEmailsRequest extends FormRequestData
{
    #[ValidArray(
        email: [new Required, new ValidEmail],
        name: [new Required, new ValidString],
    )]
    public array $people;
}

Before PHP 8.1, you wouldn't be able to write this kind of code, because you weren't allowed to use new in attributes, due to the way they are evaluated, but now you can!

Let's take a look at some important details worth mentioning.

Do you want to learn more about PHP 8.1? There's The Road to PHP 8.1. For the next 10 days, you'll receive a daily email covering a new and exiting feature of PHP 8.1; afterwards you'll be automatically unsubscribed, so no spam or followup. Subscribe now!

# Only constructed when needed

These kinds of "new values" will only be constructed when actually needed. That means that, in our first example, PHP will only create a new object of InitialState if no argument is given:

class MyStateMachine
{
    public function __construct(
        private State $state = new InitialState(),
    ) {
    }
}

new MyStateMachine(new DraftState()); // No InitialState is created
new MyStateMachine(); // But now it is

In case of attributes, for example, the objects will only be created when newInstance is called on the reflection attribute.

# Not in class properties

You should also know that you cannot use new as a default value in class properties. Supporting this functionality would introduce lots of unforeseen side effects when, for example, serializing and unserializing objects.

class MyStateMachine
{
    private State $state = new InitialState();
}

Luckily we have promoted properties which do allow a default value, since PHP will transpile the property promotion syntax, keeping the default value in the constructor argument, but not in the actual property.

Here's what the transpiled version looks like:

class MyStateMachine
{
    private State $state;
    
    public function __construct(
        State $state = new InitialState(),
    ) {
        $this->state = $state;
    }
}

# Limited input

You might have already guessed it, but you can only pass a limited set of input when constructing new objects in initializers. For example, you can't use variables, the spread operator, anonymous classes, etc. Still, it's a very welcome addition!


PHP is getting better and better with every update. Some people argue that these changes aren't strictly necessary since they don't add any new functionality to the language; but they do make our day-by-day developer lives just a little more easy, I really like that about PHP these days!

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: