Les nouveautés de PHP 8 : les attributs

Cet article est libre d'accès pour tous grâce à ceux qui soutiennent notre blog indépendant.

La nouvelle release de PHP, la version 8, apporte beaucoup de nouveautés au langage. Parmi celles-ci, nous avons déjà découvert les arguments nommés et les propriétés promues. Dans cet article, nous allons explorer les attributs de PHP 8.

Dans cette série d’articles, je reviens avec vous sur les nouveautés de la dernière version de PHP. Découvrons ensemble ce que la version 8 de PHP à de nouveau à nous apporter et comment cela pourrait nous être utile.

Si vous avez déjà travaillé avec Doctrine, Symfony ou un autre projet PHP populaire, vous connaissez sûrement les annotations. Il s'agit d'une réponse de la communauté à un manque du langage. En utilisant la syntaxe de PHPDoc, elle a permis aux projets PHP d'utiliser ce qui s'apparente dans d'autres langages à « des décorateurs ».

Dans PHP 8, les développeurs ont trouvé le moyen d'ajouter les annotations à la syntaxe native du langage.

Les attributs.

Il est possible d'ajouter des attributs aux fonctions, aux paramètres, aux classes, aux méthodes, aux propriétés et aux constantes de classes pour leur attribuer des méta-données qui seront lues et utilisées au moment du runtime.

La syntaxe des attributs est la suivante :

<?php

#[MyAttribute]
class MyClass {}

Exemple d'utilisation d'un attribut fictif

À quoi servent les attributs de PHP 8 ?

Encore une fois, les attributs serviront à peu près dans les mêmes situations que les annotations PHPDocs. Mais, rentrons un peu plus dans les détails.

Les attributs servent à ajouter des méta-données à différent symbole de notre code. Par exemple dans la déclaration de classe suivante :

<?php

class Controller
{
    #[Deprecated]
    public function productListView() {}
}

Attribut "Deprecated" sur une méthode de contrôleur

Nous attachons à la méthode produtListView une méta-donnée indiquant qu'elle est obsolète et qu'il faut éviter de l'utiliser.

Toujours dans le cas d'un contrôleur, voici un nouvel exemple :

<?php

class Controller {
    #[Deprecated]
    #[Route(path: "/")]
    public function homeAction() {}
}

Méthode avec deux attributs

La méta-donnée que l'on rajoute ici indique que homeAction est l'action qui va rendre la vue de la page d'accueil.

Donc avec les attributs, nous pouvons ajouter des données structurées a nos symboles sans pour autant les coupler à l'implémentation du symbole en question ou même l'implémentation de l'attribut lui-même.

L'implémentation générique d'une fonctionnalité et son utilisation réelle dans l'application peuvent être découplé grâce aux attributs de PHP 8.

Je m'explique. L'attribut Route est attaché sur homeAction. Mais, ce n'est pas au moment où homeAction est exécuté que notre attribut est utilisé. Cela n'aurait pas de sens. Dans ce cas, homeAction ne serait jamais exécuté puisque la route ne serait jamais définie.

De même, ce n'est pas au moment de l'implémentation de l'attribut Route qu'il est utilisé. À ce moment-là, nous ignorons encore quelle action il concerne. En cela, d'ailleurs, il rejoint la définition classique des classes et des interfaces. Vous comprendrez sûrement pourquoi dans la suite de l'article.

Comment créer vos propres attributs avec PHP 8 ?

Nous allons essayer d'implémenter un système de validation élémentaire pour des POPO (Plain Old PHP Object). Pour cela, nous avons besoin d'un attribut MaxLength qui représentera une de nos règles de validation.

Les lecteurs premium du blog ont accès aux sources complètes, fonctionnelles et commentées de l'article. Elles contiennent également plus d'exemples de cas d'utilisation.

<?php

#[Attribute]
class MaxLength
{
    public function __construct(private int $max) {}
}

Déclaration d'un attribut

Il s'agit simplement d'une classe MaxLength sur laquelle on utilise l'attribut Attribute. Nous utilisons le constructeur de la classe pour indiquer les paramètres dont aura besoin l'attribut. Si vous n'êtes pas sûr de la syntaxe utilisée ici, je vous invite à lire mon article sur les propriétés promues en PHP 8.

Nous allons utiliser ce nouvel attribut de validation sur un objet User très simple qui représente un utilisateur dans notre modèle.

<?php

class User
{
    #[MaxLength(10)]
    public string $email;
}

Utilisation d'un attribut

En utilisant l'attribut, nous définissons une règle sur le modèle User : les adresses e-mails des utilisateurs ne doivent pas dépasser 10 caractères. Très simple à comprendre, à mettre en place et à réutiliser.

Par contre, pour l'instant, il ne se passe rien. Pour que l'attribut MaxLength vérifie réellement la taille maximal d'une propriété, il va falloir lui dire comment faire. Ajoutons donc une méthode validate() à notre attribut qui implémentera la règle de validation.

<?php

#[Attribute]
class MaxLength
{
    // ...

    public function validate($value): bool
    {
        if (strlen($value) > $this->max) {
            throw new LengthException();
        }

        return true;
    }
}

Régle de validation dans l'attribut

Ensuite, il nous faut coder la partie qui va utiliser nos règles de validation. Je vous propose de créer un objet Validator qui se chargera d'appliquer les règles de validations sur chaque objet que nous lui passons.

<?php

class Validator
{
    // ...
    
    public function validate($obj)
    {
        $class = new ReflectionClass($obj);

        foreach ($class->getProperties() as $property) {
            $attributes = $property->getAttributes(MaxLength::class);

            foreach ($attributes as $ruleAttribute) {
                $rule = $ruleAttribute->newInstance();
                $rule->validate(value: $property->getValue($obj));
            }
        }
    }
}

Accéder aux attributs avec ReflectionClass

La méthode validate() de l'objet Validator prend en paramètre un objet à valider. Grâce à la Reflection API, il parcourt la liste de ses propriétés à la recherche des attributs de validations. En l'occurrence, MaxLength que nous venons de créer. Il applique ensuite la règle à la valeur de la propriété.

Plusieurs choses intéressantes ici : on accède aux attributs grâce à la méthode getAttributes() de la propriété, mais cette méthode existe aussi sur les autres symboles (classe, fonction/méthodes, etc). On peut préciser en paramètre de cette même méthode quel attribut nous recherchons en lui passant la classe de l'attribut comme ceci : getAttributes(MaxLength::class).

Il est maintenant possible d'appeler validator->validate(obj: $objetATester); et il jouera toutes les règles de validation sur l'objet passé en paramètre. Si vous ne reconnaissez pas cette syntaxe, je vous invite à lire mon article sur les arguments nommés, une autre nouveauté de PHP 8.

Bien-sûr, dans notre exemple, nous n'avons créé qu'un seul attribut de validation (MaxLength) mais il est maintenant simple d'en créer de nouveaux et de faire en sorte que Validator les prennent en compte.

Comme tout est découplé, vous n'avez pas besoin de toucher au code existant pour ajouter de nouvelles fonctionnalités. C'est essentiellement pour cela que j'apprécie cette fonctionnalité de PHP 8.

Rejoins 250+ développeurs de notre liste de diffusion et sois reçois les articles directement dans ta boite mail.

S'inscrire à la newsletter

Aucun spam. Désabonnes-toi en un seul clic à tout moment.

Si vous avez des questions ou des remarques/conseils, n'hésitez pas à laisser un commentaire plus bas ! Je serais ravis de vous lire. Et si vous aimez l'article, n'oubliez pas de le partager avec vos amis.