Séance 7 : Evénements et Listener

Symfony (comme beaucoup de framework) émét des événements à chaque étape de son cycle de vie (préparation de la requête, traitement de la requête, génération de la réponse, etc.). Ces événements peuvent être écoutés par des listeners qui peuvent modifier le comportement de Symfony.

Les événements permettent aussi d'éviter de surcharger un contrôleur ou un service. Par exemple, un utilisateur s'inscrit sur votre site. Vous souhaitez lui envoyer un email, puis une notifaction push et notifier d'autres utilisateurs d'un nouvel inscrit par exemple.

  1. Vous pourriez ajouter ces fonctionnalités dans le contrôleur qui gère l'inscription. Mais si vous avez besoin de faire la même chose dans un autre contrôleur, vous allez devoir dupliquer le code.

  2. Si vous souhaitez retirer l'un des comportements, vous allez devoir le retirer dans tous les contrôleurs qui l'utilisent.

  3. Si vous souhaitez ajouter un comportement vous allez devoir modifier tous les contrôleurs qui l'utilisent.

Les points 2 et 3 ne sont pas forcément problèmatique si vous êtes propriétaire du code. Mais si vous développez un bundle ou utilisez un bundle existant, vous ne pourrez peut etre pas modifier le code.

Dans ces trois exemples il est donc possible de définir nos événements qui pourront ensuite être écoutés par un ou plusieurs listeners, qui chacun pourra mener une action.

Nous allons donc voir dans cette partie comment écouter des événements, et comment définir nos propres événements.

Ecouter des événements (EventSubscriber)

Symfony (ou votre application) émet des événements à chaque étape de son cycle de vie. Ces événements sont écoutés par des listeners qui peuvent modifier le comportement de Symfony.

Pour écouter un événement, il faut créer une classe qui implémente l'interface Symfony\Component\EventDispatcher\EventSubscriberInterface. Cette interface contient une méthode getSubscribedEvents() qui retourne un tableau associatif. La clé est le nom de l'événement et la valeur est la méthode à appeler.

Un exemple pourrait être :

<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class MyEventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            'kernel.request' => 'onKernelRequest',
        ];
    }

    public function onKernelRequest()
    {
        // ...
    }
}

Dans cet exemple, la méthode onKernelRequest() sera appelée à chaque fois que l'événement kernel.request sera émis.

Il existe aussi la possibilité de définir des listener avec une syntaxe différente qui ne va écouter qun' seul événement précis : https://symfony.com/doc/current/event_dispatcher.html#creating-an-event-listener

Définir des événements (Event)

https://symfony.com/doc/current/components/event_dispatcher.html

Pour définir nos propres événements il faut deux choses :

  1. Définir une classe qui hérite de Symfony\Component\EventDispatcher\Event qui va contenir la description et les données de notre événement.

  2. "Emettre" l'événement avec la méthode dispatch() de l'objet Symfony\Component\EventDispatcher\EventDispatcherInterface quelque part dans notre application en lui passant un objet du type de la classe de l'événnement précédemment créée.

Classe décrivant notre événement

Pour définir notre événement, il faut créer une classe qui hérite de Symfony\Component\EventDispatcher\Event. Cette classe doit contenir les données de l'événement. Par exemple, si nous voulons définir un événement qui sera émis à chaque fois qu'un utilisateur s'inscrit, nous pourrions avoir une classe comme celle-ci :

<?php

namespace App\Event;

use Symfony\Contracts\EventDispatcher\Event;

class UserRegisteredEvent extends Event
{
    const NAME = 'user.registered';

    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function getUser()
    {
        return $this->user;
    }
}

Emettre l'événement

Pour émettre l'événement, il faut récupérer l'objet Symfony\Component\EventDispatcher\EventDispatcherInterface et appeler la méthode dispatch() en lui passant en premier paramètre le nom de l'événement et en second paramètre l'objet de l'événement.

<?php

namespace App\Controller;

use App\Event\UserRegisteredEvent;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AbstractController
{
    /**
     * @Route("/register", name="user_register")
     */
    public function register(EventDispatcherInterface $eventDispatcher): Response
    {
        // ...

        $event = new UserRegisteredEvent($user);
        $eventDispatcher->dispatch($event, UserRegisteredEvent::NAME);

        // ...
    }
}

Et bien sûr définir un listener pour écouter l'événement, comme décrit dans la partie précédente, et qui pourrait être pour notre exemple :

<?php

namespace App\EventSubscriber;

use App\Event\UserRegisteredEvent;

class UserRegisteredSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            UserRegisteredEvent::NAME => 'onUserRegistered',
        ];
    }

    public function onUserRegistered(UserRegisteredEvent $event)
    {
        // ...
    }
}

Exercices

Exercice 1

Créer un événement UserRegisteredEvent qui sera émis à chaque fois qu'un utilisateur s'inscrit. Créer un listener qui enverra un email (en utilisant notre service) à l'utilisateur pour lui dire qu'il a bien été enregistré.

Exercice 2

Créer un deuxième événement UserUpdatedEvent qui sera émis à chaque fois qu'un utilisateur met à jour son profil. Créer un listener qui enverra un email (en utilisant notre service) à l'utilisateur pour lui dire qu'il a bien été mis à jour.

Dernière mise à jour