Écrit par Nathanaël Cherrier

Les nouveautés de PHP 8 : les attributs

Publié dans , ,

Partagez l'article

twitter facebook

Cet article est libre d'accès pour tous grâce à la générosité des abonnés de Mindsers Blog qui soutiennent les publications indépendantes. Si vous appréciez le contenu que je propose, je vous invite à vous abonner aujourd'hui.

La nouvelle release de PHP, la version 8, apporte pas mal 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.

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 meta-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 meta-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 controlleur, voici un nouvel exemple :

<?php

class Controller {
    #[Deprecated]
    #[Route(path: "/")]
    public function indexAction() {}
}
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 executé que notre attribut est utilisé. Cela n'aurait pas de sens. Dans ce cas, homeAction ne serait jamais executé 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é. A ce moment là, nous ne savons pas 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 sytème de validation très basique 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'exemple 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 a 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 maximun 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 prends en paramètre un objet a 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.

...LOADING...

Nous avons rencontré une erreur en vous envoyant un e-mail. Merci de réessayer.


Si vous avez des questions ou des remarques/conseils, n'hésitez pas à laisser un commentaire plus bas ! Et si vous aimez l'article, n'oubliez pas de le partager avec vos amis. Vous pouvez aussi soutenir le blog sur Patreon.