Ce que vous devez savoir sur les Attributes de PHP 8

Ce que vous devez savoir sur les Attributes de PHP 8

Une RFC très importante vient d’être acceptée et va changer le futur de PHP. Dans cet article, nous allons parler des Attributes. Si vous travaillez avec PHP, cela vous concerne !

Ce 4 mai 2020, Benjamin Eberlei a clos les votes sur sa RFC attributes_v2, avec 51 votes d’acceptation pour 1 contre, incluant définitivement les Attributes pour PHP 8.

Il s’agit de la fin d’un très long chemin (il s’agit de la 7ème RFC sur ce sujet !), et l’aboutissement de plusieurs années de discussions.

Qu’est-ce que les Attributes ?

Les Attributes sont une forme de données structurées, qui permet d’ajouter des métadonnées à nos déclarations de classes, propriétés, méthodes, fonctions, paramètres et constantes.

Ils vont nous permettre de définir de la configuration au plus près du code, directement sur nos déclarations.

L’objectif est de remplacer les annotations « userland » que nous utilisons déjà (Doctrine, Symfony, etc) ; car ces dernières sont des hacks, parsant des docblocks non structurés.

En effet lorsque nous voyons ce code (annotation Doctrine) :

/**
 * @ORM\Column(type="string", length=255, nullable=true, name="company_email")
 */
private $companyEmail;

Il y a un fonctionnement très particulier. Pour que Doctrine connaisse votre mapping depuis le docblock, il doit être parsé comme une chaîne de caractère. C’est le rôle de la librairie doctrine/annotations utilisée par plus de 1400 paquets, et installée plus de 120 000 fois par jour1.

Il y a donc un sous-langage, parsé au runtime, qui sert à définir de la configuration statique.

Avec les Attributes, nous allons pouvoir faire la même chose, mais avec une vraie syntaxe de PHP, sans tricher, et donc avoir du code beaucoup plus robuste et clair, sans mélanger nos commentaires avec du pseudo-code.

<<ORM\Column("string", 255, true, "company_email")>>
private $companyEmail;

La syntaxe qui a été choisie est le « a-huggings » ou « tie interceptor »,
sous la forme <<AttributeName>>. Beaucoup de syntaxes ont été proposées au fil des discussions, conserver le @ n’était par exemple pas possible car déjà utilisé pour cacher des erreurs en PHP.
La syntaxe 👉AttributeName👈 a aussi été refusée pour des raisons évidentes.

Finalement, c’est la même syntaxe que pour les Attributes de HACK qui a été choisie car elle réponds à plusieurs impératifs :

  • Simple à saisir ;
  • n’entre pas en conflit avec d’autres structures du langage ;
  • facile à parser sans nécessiter de boucles complexes…

Placer des Attributes dans son code

Contrairement aux annotations, nous allons pouvoir placer des attributs sur toutes les déclarations, même les propriétés d’une méthode, ou sur des fonctions anonymes !

<<ExampleAttribute>>
class Foo
{
    <<ExampleAttribute>>
    public const FOO = 'foo';
 
    <<ExampleAttribute>>
    public $x;
 
    <<ExampleAttribute>>
    public function foo(<<ExampleAttribute>> $bar) { }
}
 
$object = new <<ExampleAttribute>> class () { };
 
<<ExampleAttribute>>
function f1() { }
 
$f2 = <<ExampleAttribute>> function () { };
 
$f3 = <<ExampleAttribute>> fn () => 1;

Exemple tiré de la RFC.

À l’intérieur des <<>>, nous devons voir le nom de l’attribut comme un appel de fonction ou un constructeur de classe (sans le mot clé new). Nous pourrons donc y placer des arguments :

<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>

Et comme pour des classes, ces noms d’attributs référencent des symboles importés !

use My\Attributes\SingleArgument;
use My\Attributes\Another;
 
<<SingleArgument("Hello")>>
<<Another\SingleArgument("World")>>
<<\My\Attributes\FewArguments("foo", "bar")>>
function foo() {}

Ma fonction foo ici possède donc 3 attributs, 2 importés et un 3éme référencé par son FQCN.

Comment créer ses propres attributs ?

Nous allons pouvoir déclarer nos propres attributs que nous pourrons utiliser sur d’autres classes, méthodes, etc de notre code. Pour que PHP reconnaisse cette classe comme attribut potentiel, il faut créer une classe qui aura elle même un attribut : PhpAttribute !

<<PhpAttribute>>
class Listener
{
    public $eventName;
 
    public function __construct(string $eventName)
    {
        $this->eventName = $eventName;
    }
}

Cette classe Listener est utilisable en tant qu’attribut :

class RequestSubscriber
{
    <<Listener("the event name")>>
    public function myMethod()
    {
    }
}

Puis, grâce à la Reflection, notre code sera capable de lire les données structurées stockées dans les attributs et d’agir en conséquence :

$reflection = new ReflectionObject($subscriber);
foreach ($reflection->getMethods() as $method) {
    $attributes = $method->getAttributes(Listener::class);
}

Les premiers usages réels

Une Pull Request est déjà prête pour ajouter un attribut Deprecated directement dans PHP. L’idée est de pouvoir déclarer des fonctions, propriétés, classes… comme étant dépréciées.

Nous le faisons actuellement avec ce code :

function test() {
    trigger_error("Function test is deprecated, use foobar", E_USER_DEPRECATED)
}

Nous pourrions bientôt le faire via un attribut :

<<Deprecated("Function test is deprecated, use foobar")>>
function test() {}

Un autre usage annoncé concerne PHPUnit 11. Sebastian Bergmann a tweeté qu’il souhaitait utiliser la feature pour la version 11 de son outil de test, prévu pour Février 2022.

Dans un premier temps, nous allons probablement voir des usages directement dans PHP et ses extensions. Par exemple, l’extension Opcache utilise une annotation en docblock à l’heure actuelle, qui pourrait avantageusement être remplacée par un attribut.

Le futur c’est bientôt

PHP 8 devrait sortir en fin d’année2 et nous aurons donc, en plus du JIT, ces nouveaux attributs avec lesquels travailler.

Nous allons pouvoir changer de paradigme et placer des métadonnées au plus près de nos instructions, et profiter de ce que les développeurs Java, C#, C++, Rust et Hack utilisent déjà.

Des outils comme Rector pourrons nous aider à migrer nos annotations en attributs, pour les librairies en ajoutant le support. Cependant tout ne sera pas simple et certaines syntaxes devront probablement changer.

Par exemple, les attributs imbriqués ne sont pas possibles, et nous ne pourrons pas, avec cette première version en tout cas, reproduire à l’identique ce type d’annotation :

/**
* @ORM\JoinTable(
*     name="foobar",
*     joinColumns={@ORM\JoinColumn(name="company_id", referencedColumnName="id")},
*     inverseJoinColumns={@ORM\JoinColumn(name="member_id", referencedColumnName="id")}
* )
*/

Quoi qu’il en soit nous avons hâte de pouvoir utiliser ces structures de données robustes et performantes ; qui pourraient aussi bénéficier bientôt des Named Arguments actuellement en cours de discussion.

Il ne nous reste qu’à féliciter et remercier chaleureusement Benjamin Eberlei, Martin Schröder, Dmitry Stogov et toutes les personnes impliquées dans cette évolution du langage ! Vivement PHP 8 🐘 !

Nos formations sur le sujet

  • Logo Symfony

    Symfony

    Formez-vous à Symfony, l’un des frameworks web PHP les plus connus au monde

blog comments powered by Disqus