Cours Laravel 5.3 – plus loin – les événements

Il y a plusieurs façons de programmer. En général la plus simple et lisible consiste à procéder de façon séquentielle : on a une suite d’instructions (modulables par des conditions) qui sont toujours les mêmes. Le flux est facile à suivre.

Mais on a aussi l’approche événementielle. On va réagir à des événements susceptibles de se produire. On peut mettre ainsi en place un ensemble constitué par une partie qui détecte des événements et une autre qui les gère.

Laravel permet de gérer avec facilité les événements comme on va le voir dans ce chapitre.

Le design pattern Observateur

Le design pattern Observateur (observer) établit une relation de type 1:n entre des objets.  Si l’objet côté 1 change d’état il en informe les objets côté n pour qu’ils puissent agir en conséquence. Ça implique une intendance et les objets doivent pouvoir s’inscrire et se mettre à l’écoute des événements du sujet.

Voici une visualisation de ce design pattern :

On a un sujet et des observateurs. Les observateurs doivent s’inscrire (on dit aussi s’abonner) auprès du sujet. Lorsque le sujet change d’état il notifie tous ses abonnés. Un observateur peut aussi se désinscrire, il ne recevra alors plus les notifications.

Vous pouvez trouver une description détaillée de ce design pattern ici.

Organisation du code

Laravel implémente ce design pattern et le rend très simple d’utilisation. On va avoir :

  • des classes Event placées dans le dossier app/Events :

  • des classes Listener placées dans le dossier app/Listeners :

Ces dossiers n’existent pas dans l’installation de base, ils sont ajoutés dès qu’on crée la première classe avec Artisan qui dispose de commandes pour le faire :

Les événements déjà présents dans Laravel

Mais il n’y a pas que les événements que vous allez créer. Vous pouvez utiliser les événements existants déjà dans le framework qui en propose au niveau de l’authentification :

  • tentative de connexion (login),
  • connexion (login) réussie,
  • déconnexion (logout) réussie.

On trouve aussi des événements avec Eloquent :

  • creating,
  • created,
  • updating,
  • updated,
  • saving,
  • saved,
  • deleting,
  • deleted,
  • restoring, restored.

Pour tous ces cas vous n’avez donc pas à vous inquiéter de l’événement et du déclencheur qui existent déjà, il faudra juste mettre en place une écoute (Listener).

Le fournisseur de service (service provider)

Il y a un fournisseur de service dédié aux événements :

Dans l’application d’exemple on a ce code :

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'Illuminate\Auth\Events\Login' => ['App\Listeners\LoginSuccess'],
        'Illuminate\Auth\Events\Logout' => ['App\Listeners\LogoutSuccess'],
        'App\Events\UserAccess' => ['App\Listeners\UserAccess']
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();
    }
}

On a une propriété listen pour les abonnements. C’est un simple tableau qui prend l’événement (event) comme clé et l’observateur (listener) comme valeur.

On veut savoir quand quelqu’un se connecte ou se déconnecte. On prévoit donc des observateurs dans le provider :

protected $listen = [
	'Illuminate\Auth\Events\Login' => ['App\Listeners\LoginSuccess'],
	'Illuminate\Auth\Events\Logout' => ['App\Listeners\LogoutSuccess'],
	...
];

Vous pouvez trouver ces événements dans la documentation.

Laravel prévoit de placer les observateurs (listeners) dans ce dossier en tant que classes :

Le statut de l’utilisateur dans l’application

Pour simplifier la gestion le statut de l’utilisateur (son rôle) est enregistré dans la session. On a 3 rôles :

  • Administrateur (admin) : avec tous les droits,
  • Rédacteur (redac) : avec le droit de gérer des articles,
  • Utilisateur (user) : avec juste le droit de laisser des commentaires.

Et on ajoute visitor pour un utilisateur non connecté.

La connexion

Donc dès que quelqu’un se connecte on doit regarder son rôle et le mémoriser dans la session.

Si on regarde l’observateur (listener) :

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use App\Services\Statut;

class LoginSuccess
{
    /**
     * Handle the event.
     *
     * @param  Login  $login
     * @return void
     */
    public function handle(Login $login)
    {
        Statut::setLoginStatut($login);
    }
}

On fait appel à un service avec une méthode statique :

<?php

namespace App\Services;

class Statut
{
    /**
     * Set the login user statut
     *
     * @param  Illuminate\Auth\Events\Login $login
     * @return void
     */
    public static function setLoginStatut($login)
    {
        session(['statut' => $login->user->getStatut()]);
    }

    ...
}

Il faut aller voir dans le modèle App\Models\User pour trouver la méthode :

public function getStatut()
{
    return $this->role->slug;
}

On passe par la relation entre les utilisateurs et les rôles pour aller chercher l’information à mettre dans la session.

La deconnexion

Et évidemment en cas de déconnexion c’est le même processus avec le listener LogoutSuccess :

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Logout;
use App\Services\Statut;

class LogoutSuccess
{
    /**
     * Handle the event.
     *
     * @param  Logout  $event
     * @return void
     */
    public function handle(Logout $event)
    {
        Statut::setVisitorStatut();
    }
}

On utilise encore le service :

public static function setVisitorStatut()
{
    session(['statut' => 'visitor']);
}

Accès à une page

Il faut considérer un dernier cas : lorsque l’utilisateur accède à une page on va vérifier s’il y a un statut mémorisé en session et si ce n’est pas le cas en créer un.

Cette fois ce n’est pas un événement qui existe comme login ou logout, il nous faut donc le créer.

On a donc cette fois une classe Event :

Avec ce code :

<?php

namespace App\Events;

class UserAccess extends Event
{
    //
}

Oui il n’y a que la classe parce qu’on a rien de particulier à transmettre dans ce cas  .

On doit donc déclencher cet événement quand un utilisateur accède à une page

On va donc passer par un middleware. Regardez dans la classe App\Http\Middleware\App :

public function handle(Request $request, Closure $next)
{
    event(new UserAccess);

    return $next($request);
}

On utilise l’helper event pour déclencher l’événement. Sans l’helper il faudrait utiliser la façade :

Event::fire(new UserAccess);

Au niveau de l’inscription on a vu qu’on a ceci :

protected $listen = [
	...
	'App\Event\UserAccess' => ['App\Listeners\UserAccess']
];

Voici le code du listener :

<?php

namespace App\Listeners;

use App\Events\UserAccess as UserAccessEvent;
use App\Services\Statut;
use App\Services\Locale;

class UserAccess
{
    /**
     * Handle the event.
     *
     * @param  UserAccess  $event
     * @return void
     */
    public function handle(UserAccessEvent $event)
    {
        Statut::setStatut();

        Locale::setLocale();
    }
}

On appelle encore la méthode dans le service :

public static function setStatut()
{
    if (!session()->has('statut')) {
        session(['statut' =>  auth()->check() ?  auth()->user()->getStatut() : 'visitor']);
    }
}

On a la logique ici : si on n’‌a pas de statut dans la session on regarde si l’utilisateur est connecté, si c’est le cas on va chercher son statut, sinon on lui affecte le statut visitor.

Remarquez aussi dans le listener la gestion de la locale qu’on a vue dans un précédent chapitre.

Pour avoir la documentation complète sur ce sujet c’est dans la documentation.

La programmation événementielle en PHP est un certain style de programmation. Elle n’a rien d’obligatoire parce qu’on peut toujours s’en passer si on préfère coder de façon plus classique. Mais il y a de nombreuses situations où elle présente un intérêt certain, en particulier quand un événement possède plusieurs observateurs indépendants les uns des autres. D’autre part Laravel offre des événements précodés, comme on l’a vu dans ce chapitre, qu’il serait dommage de ne pas utiliser.

En résumé

  • Laravel comporte un système complet et simple de gestion des événements.
  • Laravel comporte déjà des événements pour l’authentification et la gestion des données avec Eloquent.
  • On doit créer une classe Event et une classe Listener pour mettre en place une gestion événementielle et ensuite définir un déclencheur.

Laisser un commentaire