Site non compatible avec cette vieillerie d'Internet Explorer. Pour une expérience complète, voir la liste des navigateurs supportés.
Thumbnail

 Memo [Symfony 4.4] Créer un Event Subscriber en réponse à une connexion

Symfony dispose d'un système d'events avec lequel on peut intéragir. Au cours de sa vie, l'application déclenche régulièrement des évènements, nous verrons donc ici au travers d'un exemple comment mettre à jour une date de "dernière connexion" en base de données après qu'un utilisateur se soit connecté avec succès.


Post-it | Symfony

Auteur : Guillaume M.
Créé le : 10 Jun 2021
Dernière mise à jour : 10 Jun 2021

Event Listener et Event Subscriber


Ils existent 2 manières d'écouter les events, avec un Listener ou avec un Subscriber. Les 2 peuvent être utilisés dans la même application indistinctement, finalement c'est avant tout une question de goût. Mais alors, laquelle de ces 2 méthodes choisir ? Et bien voici un petit descriptif de chacun :

  • Les Subscribers sont plus faciles à réutiliser du fait qu'un Subscriber garde les events directement dans sa classe plutôt que dans la definition du service
  • Les Listeners sont plus fexibles car les bundles peuvent les activer/désactiver à leur guise en fonctions de certaines valeurs de configuration

Ici j'ai donc fait le choix d'utiliser le Subscriber car plus simple d'utilisation et je n'ai pas besoin de la flexibilité d'un Listener. Voyons maintenant comment mettre cela en place pour capter l'event relatif à une connexion utlisateur réalisée avec succès.

 

Choisir un event


Dans un premier temps il nous faut savoir précisément quel event nous souhaitons "capter" et comme indiqué dans la description nous cherchons à réagir lorsqu'un utilisateur se connecte avec succès. Une simple recherche dans la doc de Symfony nous indique ceci : https://symfony.com/doc/4.4/components/security/authentication.html#authentication-events.

On notera la constante d'event ainsi que l'argument passé au listener, cela sera utile lors de la création de notre classe.

Comme nous pouvons le constater il existe plusieurs types events relatifs à l'authentification et une petite lecture rapide nous indique que l'event que nous cherchons est security.interactive_login. En effet, c'est cet event qui est dispatché lorsqu'un utlisateur se connecte de lui-même et l'event security.authentication.success quand à lui ne nous intéresse pas car celui-ci ne fait pas de distinction entre une connexion manuelle ou automatique (via le système de session ou un provider) or ici nous souhaitons capter la connexion manuelle.

 

Création du Subscriber


Un Subscriber est une classe héritant de l'interface EventSubscriberInterface et dans laquelle nous devons écrire une fonction statique pour indiquer au système d'events à quel(s) type(s) d'event notre subscriber va-t-il "souscrire", ce qui nous donne dans sa forme la plus simple :

//src/EventSubscriber/LoginSuccessSubscriber.php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class LoginSuccessSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [];
    }
}

À noter qu'ici la classe se situe dans un dossier EventSubscriber créé au préalable dans le dossier src. C'est une norme de la documentation.

La fonction getSubscribedEvents() doit renvoyer les events auxquels nous voulons souscrire (car oui nous pouvons souscrire à autant d'events que nous le souhaitons) et pour cela nous avons besoin de la constante d'un event et d'indiquer le nom de la fonction qui doit être appelé lorsque que l'event est dispatché, ce qui nous donne :

//src/EventSubscriber/LoginSuccessSubscriber.php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class LoginSuccessSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            SecurityEvents::INTERACTIVE_LOGIN => "onLoginSuccess"
        ];
    }

    public function onLoginSuccess(InteractiveLoginEvent $event)
    {
        var_dump("Login Success !");
        die();
    }
}

La fonction renvoie donc maintenant un tableau associatif contenant en clé la constante de l'event et en valeur le nom de la fonction qui doit être appelée et que nous avons également ajoutée avec en paramètre l'objet passé lors de son appel. Toutes ces informations se situe dans le tableau vu précédemment.

À partir d'ici, nous pouvons vérifier que notre Subscriber est bien enregistré avec la commande :

php bin/console debug:event-dispatcher security.interactive_login

Pour afficher une liste complète des Listeners/Subscribers, taper la commande ci-dessus sans préciser le nom de l'event.

Si nous ne voyez pas votre Subscriber dans la liste, c'est peut-être que votre application n'est pas configuré de manière automatique pour les enregistrements/injections, dans ce cas voir du côté du fichier de config services.yaml.
Prendre soin aussi de vérifier que le nom du fichier et de la classe soit identiques.

Bien ! Si l'application dispose du système de login, nous pouvons tester notre Subscriber, il est fonctionnel !

 

Modifier la date de dernière connexion en base de données


Ici je suppose que l'application contient une table User contenant une colonne last_connection ! Je ne vais pas détailler le nouveau code, il est plutôt simple à comprendre, ce n'est ni plus ni moins qu'une mise à jour en BDD en réponse à l'event :

<?php
//src/EventSubscriber/LoginSuccessSubscriber.php

namespace App\EventSubscriber;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class LoginSuccessSubscriber implements EventSubscriberInterface
{
    private $entityManager;

    public function __construct(EntityManagerInterface $em)
    {
        $this->entityManager = $em;
    }

    public static function getSubscribedEvents()
    {
        return [
            SecurityEvents::INTERACTIVE_LOGIN => "onLoginSuccess"
        ];
    }

    public function onLoginSuccess(InteractiveLoginEvent $event)
    {
        // Récupération de l'id utilisateur
        $userId = $event->getAuthenticationToken()->getUser()->getId();

        // Récupération en base de l'utilisateur concerné + mise à jour
        $user = $this->entityManager->getRepository(User::class)->find($userId);
        $user->setLastConnection(new \DateTime());
        $this->entityManager->flush();

        // Ici nous pourrions, par exemple, ajouter également une notification flash etc...
    }
}

Cet exemple est vraiment simpliste mais fonctionnel, on pourrait envisager d'avoir plusieurs fonctions avec un ordre de priorité différent pour un même event ou bien ajouter d'autres events à notre subscriber etc. Pour plus d'informations sur le sujet voir la doc ;).