PHP 8.1 - Propriété readonly

PHP 8.1 a introduit une nouvelle fonctionnalité vraiment intéressante : les propriétés readonly.

Celle-ci permet de déclarer que la valeur de la propriété ne peut être modifiée qu’une seule fois. Bien souvent on va vouloir le faire dans un constructeur mais ça peut être fait dans une autre méthode. En revanche, impossible de le faire à l’extérieur de la classe.

C’est bien toute modification qui est impossible : c’est à dire qu’il est impossible d’utiliser les opérateurs d’incrémentation/décrémentation, unset(), etc…

class Foo
{
    public readonly string $bar;

    public function __construct(string $bar) 
    {
        $this->bar = $bar;
    }
}

De cette manière, on pourra accéder en lecture à la propriété bar mais il sera impossible de la modifier après l’instantiation de l’objet.

$foo = new Foo('baz');

$foo->bar = 'foobar';
// Fatal error: Uncaught Error: Cannot modify readonly property
// Foo::$bar

Ce qui est très cool ici, c’est le fait de pouvoir se passer de beaucoup de code pour protéger l’accès aux propriétés des objets (l’encapsulation).

Si j’avais voulu reproduire le même comportement avant PHP 8.1 il aurait fallu faire quelque chose comme ça :

class Foo
{
    private string $bar;

    public function __construct(string $bar) 
    {
        $this->bar = $bar;
    }

    public function getBar(): string
    {
        return $this->bar;
    }
}

On voit qu’on a beaucoup plus de code. En encore, je dis “reproduire” mais ce n’est pas vrai puisque dans cette dernière implémentation, $bar peut être modifiée (par une ou plusieurs méthodes de la classe) après l’instantiation, ce qui n’est pas possible avec readonly.

Bien sur cela fonctionne également avec la promotion de propriété de constructeur :

class Foo
{
    public function __construct(public readonly string $bar) 
    {
    }
}

Quelques détails

Propriétés typées uniquement

Cela fonctionne uniquement sur les propriétés typées. En effet, si la propriété n’est pas typée, elle a null pour valeur par défaut et donc ne peut plus être affectée par la suite.

Pas de valeur par défaut

Il est impossible de définir une valeur par défaut à une propriété readonly sauf si celle-ci est une promotion de propriété de constructeur.

class Foo
{
    public readonly string $bar = 'baz'; // KO

    public function __construct(public readonly string $bar = 'baz') // OK
    {
    }
}

Pas d’immutabilité sur les objets

Attention, utiliser readonly sur un objet ne lui assure pas l’immutabilité :

class Bar {
    public string $baz;
}

class Foo {
    public function __construct(public readonly Bar $bar) {
    }
}

$bar = new Bar();
$bar->baz = 'foobar';

$foo = new Foo($bar);
$bar->baz = 'barfoo'; // OK

Pas d’héritage

Il est impossible d’ajouter ou enlever le readonly sur une propriété via un héritage.

Conclusion

Ce qu’il faut retenir c’est qu’on peut écrire (et donc maintenir, etc) nettement moins de code. Cela épure énormément nos classes (en particulier nos DTOs et autre Value Object) et on apprécie !

Attention quand même à bien comprendre les impacts, les possibilités et les contraintes que cela induit.