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 améliorer les performances et la pertinence des recherches sur le site e-commerce, JoliCode a réalisé un audit approfondi du moteur Elasticsearch existant. Nous avons optimisé les processus d’indexation, réduisant considérablement les temps nécessaires tout en minimisant les requêtes inutiles. Nous avons également ajusté les analyses pour mieux…
Nous avons entrepris une refonte complète du site, initialement développé sur Drupal, dans le but de le consolider et de jeter les bases d’un avenir solide en adoptant Symfony. La plateforme est hautement sophistiquée et propose une pléthore de fonctionnalités, telles que la gestion des abonnements avec Stripe et Paypal, une API pour l’application…
La nouvelle version du site naissance.fr développée s’appuie sur Symfony 2 et Elasticsearch. Cette refonte propose un tunnel d’achat spécialement développé pour l’application. Aujourd’hui, le site est équipé d’une gestion d’un mode d’envoi des faire-parts différé, de modification des compositions après paiement et de prise en charge de codes promotionnels…