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
Pour renforcer Ouranos, le référentiel de données dédié à la signalisation ferroviaire, SNCF Réseau a sollicité JoliCode afin d’auditer et améliorer la qualité technique de l’application. Développée en Symfony et API Platform, Ouranos est au cœur des processus de production, utilisé aussi bien en interne que par l’industrie. Notre intervention s’est…
Dans le cadre d’une refonte complète de son architecture Web, le journal en ligne Mediapart a sollicité l’expertise de JoliCode afin d’accompagner ses équipes. Mediapart.fr est un des rares journaux 100% en ligne qui n’appartient qu’à ses lecteurs qui amène un fort traffic authentifiés et donc difficilement cachable. Pour effectuer cette migration, …
Après avoir monté une nouvelle équipe de développement, nous avons procédé à la migration de toute l’infrastructure technique sur une nouvelle architecture fortement dynamique à base de Symfony2, RabbitMQ, Elasticsearch et Chef. Les gains en performance, en stabilité et en capacité de développement permettent à l’entreprise d’engager de nouveaux marchés…