Constructor property promotion in PHP 8

If there is one redundant and boring thing in object-oriented programming, it is the definition of object constructors. In most cases, the majority of the code written is actually information that the interpreter or compiler could understand on its own.

In this series of articles, I come back with you on the new features of the latest version of PHP. Let's discover together what PHP version 8 has new to offer us and how it could be useful to us.

What I have just stated is not very nuanced, it is true. But if I go through the codebases of my previous projects, the majority of the constructors I have written are only used to initialize instance variables (properties).

Let's take an example in PHP to illustrate this:

<?php

class MyClass extends ParentClass {
    private string $title;
    private bool $isUnique;
    private bool $isVariadic;
    
    public function __construct(
        string $title,
        bool $isUnique = false,
        bool $isVariadic = false,
        ?string $desc = null,
    ) {
        parent::__construct($text);
        
        $this->title = $title;
        $this->isUnique = $isUnique;
        $this->isVariadic = $variadic;
    }
}

Here is a typical example of everything I described above. There are three properties that are repeated as arguments to the constructor and again twice at assignment time inside the constructor. Types are also repeated twice.

It seems to see almost the same code three times. It's boring to read, but even more tiring to write or maintain: this way of doing things increases the risk of error. Why? If I change any of the instance properties, I definitely need to remember to change the constructor property declarations and/or assignments as well.

PHP 8 provides a solution to this problem through the promotion of constructor properties.

<?php

class MyClass extends ParentClass {
    public function __construct(
        private string $title,
        private bool $isUnique = false,
        private bool $isVariadic = false,
        ?string $desc = null,
    ) {
        parent::__construct($text);
    }
}

The two examples are identical. But watch how promoted properties help make code simpler, more readable, and smarter. All the information we need to understand the code is on the same line, not split into three separate places.

Again, this is not a breakthrough feature or something truly new. Other languages like TypeScript have already had this feature for a while.

Not usable in all situations

Some of you may have already thought of this, but there are cases where using constructor property promotion is not going to be possible.

This functionality means: creating instance variables automatically and assigning them. Which is not possible in an abstract constructor, for example.

<?php

interface MyInterface {
    public function __construct(
        private string $title, // Erreur
    ) {}
}

abstract class MyClass {
    abstract public function __construct(
        private string $title, // Erreur
    ) {}
}

Another case where this functionality is not usable is the case of callables. The callable type is not supported for method properties. It is therefore impossible to promote a callable property since it does not exist.

<?php

class MyClass {
    public function __construct(
        private callable $fn, // Erreur
    ) {}
}

I would also add that promoted properties cannot be used with implicitly nullable types. Or rather, a nullable type cannot be implicit with a promoted property.

<?php

class MyClass {
    public function __construct(
        private string $desc = null, // Erreur
        private ?string $text = null, // OK
    ) {}
}

As long as you explicitly indicate that the property can be null, PHP won't mind.

I've used it often in TypeScript, and I find promoting constructor properties really useful in my daily life as a developer. Let me explain: first, I write three times less code. Then, my reading and understanding of the code is easier for a given instance variable.

<?php

class MyClass extends ParentClass {
    public function __construct(
        private string $title,
        private bool $isUnique = false,
        private bool $isVariadic = false,
        string $desc = null,
    ) {
        parent::__construct($text);
    }
}

But this method also gives me information about the other properties of the constructor. In the example above, did you notice the $desc property? We don't use constructor property promotion for it, which gives me another piece of information: $desc is not a classic case of an instance variable, the developer had to do something else with it, and it might be interesting to watch what it's all about.

It's contextual: since the developer who wrote the code used promoted properties, why didn't he promote all the properties? Probably because he had no choice. Perhaps he had particular tests to do on this property. Or, like in our example, maybe it needed to pass it to the parent class. It makes the code smarter, but it also makes the reading of the code smarter by indicating the points of attention to be had.

And as it is contextual, as you can imagine, the reasoning that I have just described will not work every time. If none of the properties are promoted, that doesn't necessarily mean that all properties of that constructor are special cases. Maybe the developer who wrote the code doesn't yet know that the promoted properties exist, maybe he doesn't want to use this feature, or maybe his code was written before the arrival of PHP 8. Anyway, you get the idea.

I will end this article by reminding that Promoted Properties is a lazy feature and developer being lazy, I am sure you will all be happy to adopt it in your future projects. In the meantime, here's some reading: