26 novembre 2020 : PHP 8 sort officiellement, et avec lui les attributs, cette syntaxe entre crochets qui permet d’attacher des métadonnées structurées directement à une classe, une méthode ou une propriété. Pour un contrôleur REST WordPress chargé de dix routes ou plus, cette nouveauté ouvre une question simple : peut-on remplacer le tableau imbriqué de register_rest_route() par une déclaration posée directement sur la méthode qu’elle enregistre ?
La réponse est oui, à condition d’accepter que ce ne soit qu’une couche de confort au-dessus du mécanisme natif, et non un remplacement de celui-ci. WordPress ne lit aucun attribut PHP par lui-même pour enregistrer une route ; il faut donc construire soi-même la logique qui transforme l’attribut en appel à register_rest_route().
Ce qu’apporte la déclaration par attribut
Un attribut personnalisé, défini une seule fois dans le projet, peut se poser directement au-dessus de chaque méthode de contrôleur qu’il concerne :
#[Attribute]
class Route {
public function __construct(
public string $chemin,
public string $methode = 'GET',
) {}
}
class ControleurCatalogue {
#[Route( chemin: '/produits', methode: 'GET' )]
public function lister_produits( WP_REST_Request $request ) {
// ...
}
}
La méthode et sa route restent visuellement collées l’une à l’autre, ce qui évite l’aller-retour permanent entre un fichier de déclaration de routes et le fichier contenant la logique métier. Sur un contrôleur qui gère une quinzaine d’endpoints, ce simple rapprochement change sensiblement le confort de lecture.
Le mécanisme derrière l’attribut

Un attribut, seul, ne fait rien exécuter automatiquement. Il faut une classe qui, via l’API de réflexion de PHP, parcourt les méthodes d’un contrôleur, repère celles qui portent l’attribut Route, et appelle register_rest_route() à leur place, généralement accroché au hook rest_api_init :
add_action( 'rest_api_init', function () {
$reflexion = new ReflectionClass( ControleurCatalogue::class );
foreach ( $reflexion->getMethods() as $methode ) {
foreach ( $methode->getAttributes( Route::class ) as $attribut ) {
$route = $attribut->newInstance();
register_rest_route( 'catalogue/v1', $route->chemin, array(
'methods' => $route->methode,
'callback' => array( new ControleurCatalogue(), $methode->getName() ),
) );
}
}
} );
Cette couche de réflexion n’est pas gratuite : elle s’exécute à chaque chargement de rest_api_init, donc à chaque requête REST, ce qui ajoute un coût mesurable sur un contrôleur volumineux si le résultat de l’analyse n’est jamais mis en cache.
Les limites face à register_rest_route
- Aucun schéma d’argument riche (type, validate_callback, sanitize_callback) sans réécrire tout ce mécanisme dans l’attribut lui-même
- Perte de la vue d’ensemble : la liste complète des routes du plugin n’existe plus dans un seul fichier facile à parcourir
- Un coût de réflexion à chaque initialisation de l’API REST, à mettre en cache manuellement pour rester raisonnable en production
- Aucune garantie de compatibilité avec les extensions tierces qui inspectent les routes déclarées de façon classique
Un compromis plutôt qu’un remplacement
Sur les projets où ce motif a été testé, la meilleure approche a consisté à garder register_rest_route() pour tout ce qui touche à la validation fine des arguments, et à réserver l’attribut aux routes les plus simples, sans schéma complexe, où le gain de lisibilité l’emporte clairement sur la perte de vue d’ensemble. Une route de lecture simple, sans paramètre sensible, se prête bien à l’attribut ; une route d’écriture avec dix champs à valider reste plus sûre en tableau classique.
En résumé
Les attributs PHP 8 offrent une façon élégante de rapprocher la déclaration d’une route de la méthode qui la traite, mais ils ne remplacent à aucun moment le moteur de register_rest_route() : ils ne font que l’appeler différemment, via une couche de réflexion à construire soi-même. Sur un contrôleur simple, le gain de lisibilité est réel ; sur un contrôleur qui exploite pleinement la validation de schéma native, revenir au tableau classique reste souvent le choix le plus sûr.