Comment utiliser les attributs PHP sur un contrôleur Symfony ?
PHP 8.0 a introduit les attributs, et c’est une excellente fonctionnalité ! Le code devient plus lisible, plus simple à écrire, et bénéficie pleinement de la coloration syntaxique et du linting de notre IDE.
Symfony a très vite adopté cette nouveauté. On les utilise aujourd’hui partout : pour configurer les routes, ajouter des contraintes de validation ou déclarer des listeners.
Dans cet article, nous allons voir comment créer et ajouter un attribut PHP personnalisé sur un contrôleur pour lui injecter un comportement automatique, comme du logging.
Info
Nous avions déjà parlé des attributs en 2021 pour limiter le débit de vos API (Rate Limit). Cependant, Symfony a bien évolué depuis, et leur utilisation est devenue encore plus simple.
Section intitulée 1-creer-un-attribut1. Créer un attribut
La première étape consiste à créer une classe PHP classique. Pour indiquer qu’elle servira d’attribut, on lui ajoute elle-même l’attribut natif #[\Attribute] :
namespace App\AuditLog;
use Psr\Log\LogLevel;
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_CLASS)]
class Loggable
{
public function __construct(
public string $level = LogLevel::INFO,
) {
}
}
Les paramètres du constructeur sont optionnels. Ils permettent de passer des options au moment où on utilise l’attribut. Ici, notre propriété $level sert à définir le niveau de log souhaité.
Section intitulée 2-utiliser-l-attribut-sur-un-controleur2. Utiliser l’attribut sur un contrôleur
Une fois notre classe créée, nous pouvons l’appliquer directement sur un contrôleur, soit sur la classe entière, soit sur une méthode (action) spécifique :
namespace App\Controller;
use App\AuditLog\Loggable;
use Psr\Log\LogLevel;
use Symfony\Bridge\Twig\Attribute\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
#[Loggable()]
final class HomepageController extends AbstractController
{
#[Loggable(LogLevel::CRITICAL)]
#[Route('/', name: 'app_homepage')]
#[Template('homepage/index.html.twig')]
public function index(): void
{
}
}
Ici, la méthode index hérite de la configuration par défaut de la classe, et ajoute un nouveau niveau de log plus critique (CRITICAL).
Section intitulée 3-creer-le-listener-pour-activer-le-comportement3. Créer le listener pour activer le comportement
Pour que notre attribut serve à quelque chose, il faut intercepter l’appel du contrôleur. Symfony déclenche l’événement ControllerEvent juste avant d’exécuter l’action d’un contrôleur. C’est le moment idéal pour vérifier la présence de notre attribut.
Selon votre version de Symfony, l’implémentation est devenue de plus en plus simple.
Section intitulée avec-symfony-6–2Avec Symfony 6.2+
On utilise la méthode getAttributes() de l’événement pour récupérer notre attribut et appliquer notre logique :
namespace App\AuditLog;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
class LoggerListener
{
public function __construct(
private readonly LoggerInterface $logger = new NullLogger(),
) {
}
#[AsEventListener(priority: -100)]
public function logSymfony62(ControllerEvent $event): void
{
foreach ($event->getAttributes()[Loggable::class] ?? [] as $attribute) {
$this->logger->log($attribute->level, 'Controller is loggable', [
'controller' => $event->getController(),
]);
}
}
}
Section intitulée avec-symfony-8–1Avec Symfony 8.1+
Symfony 8.1 a introduit des événements spécifiques aux attributs. Plus besoin de boucler manuellement, l’événement contient directement l’attribut ciblé :
use Symfony\Component\HttpKernel\Event\ControllerAttributeEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LoggerListener
{
#[AsEventListener(KernelEvents::CONTROLLER . '.' . Loggable::class, priority: -100)]
public function logSymfony81(ControllerAttributeEvent $event): void
{
$this->logger->log($event->attribute->level, 'Controller is loggable', [
'controller' => $event->kernelEvent->getController(),
]);
}
}
Section intitulée avec-symfony-8–2Avec Symfony 8.2+
La version 8.2 simplifira encore la syntaxe grâce à un attribut dédié (AsControllerAttributeListener) qui cible directement notre classe :
use Symfony\Component\HttpKernel\Attribute\AsControllerAttributeListener;
use Symfony\Component\HttpKernel\Event\ControllerAttributeEvent;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
class LoggerListener
{
#[AsControllerAttributeListener(ControllerEvent::class, Loggable::class)]
public function logSymfony82(ControllerAttributeEvent $event): void
{
$this->logger->log($event->attribute->level, 'Controller is loggable', [
'controller' => $event->kernelEvent->getController(),
]);
}
}
Section intitulée conclusionConclusion
Les attributs apportent une grande souplesse à l’écosystème PHP, et Symfony propose des outils parfaits pour les exploiter.
Grâce à eux, vous pouvez ajouter des comportements à vos contrôleurs de manière propre et déclarative, sans polluer vos méthodes avec du code répétitif.
Bien sûr, Symfony intègre déjà nativement des attributs puissants pour gérer la sécurité (#[IsGranted]), le cache (#[Cache]), ou encore le rate limiting (#[RateLimit]). Mais créer vos propres attributs ouvre la porte à des cas d’usage métiers très intéressants :
- Le Feature Flipping (
#[FeatureFlag('new-dashboard')]) : pour activer ou désactiver l’accès à une route selon le déploiement progressif d’une fonctionnalité ; - La télémétrie et l’observabilité (
#[TrackActivity('checkout')]) : pour envoyer des statistiques précises à des outils tiers comme OpenTelemetry ou Plausible dès qu’un utilisateur visite une page clé ; - La transformation de réponse (
#[Serialize]) : pour intercepter le retour de vos contrôleurs et le formater automatiquement selon un standard précis (comme le format JSON d’une API spécifique).
Les possibilités n’ont de limite que votre imagination !
Commentaires et discussions
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !
Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise
Ouibus a pour ambition de devenir la référence du transport en bus longue distance. Dans cette optique, les enjeux à venir de la compagnie sont nombreux (vente multi-produit, agrandissement du réseau, diminution du time-to-market, amélioration de l’expérience et de la satisfaction client) et ont des conséquences sur la structuration de la nouvelle…
JoliCode a formé l’équipe de développement d’Evaneos aux bonnes pratiques pour l’écriture de tests unitaires efficaces et utiles. Nous en avons également profité pour mettre en place une plateforme d’intégration continue pour accompagner l’évolution de la plateforme.
LOOK Cycle bénéficie désormais d’une nouvelle plateforme eCommerce disponible sur 70 pays et 5 langues. La base technique modulaire de Sylius permet de répondre aux exigences de LOOK Cycle en terme de catalogue, produits, tunnel d’achat, commandes, expéditions, gestion des clients et des expéditions.