aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Développement Web

Laravel Event et Listener : guide du code découplé

Un Laravel event est la manière la plus propre d'annoncer qu'« il s'est passé quelque chose » dans votre application tout en gardant complètement à part le code qui y réagit. Au lieu d'entasser trois tâches différentes — envoyer un e-mail de bienvenue, écrire une entrée de log et mettre à jour une statistique — dans un seul contrôleur lorsqu'un utilisateur s'inscrit, vous publiez un seul événement et les listeners qui s'y abonnent se partagent le travail. Le résultat est une base de code faiblement couplée, testable et ouverte à l'évolution. Dans cet article, nous allons construire l'architecture event/listener depuis zéro, la mettre en file d'attente et voir comment l'utiliser dans des scénarios réels, étape par étape.

Quelle est l'idée derrière event et listener ?

L'idée est simple : un event est un objet message qui représente « quelque chose qui s'est produit » — par exemple OrderShipped. Un listener est le code qui réagit à cet événement — comme envoyer un e-mail au client. Le code qui publie l'événement n'a pas besoin de savoir qui écoute. C'est le classique patron publish/subscribe, et il apporte quelques avantages clés :

  • Couplage faible : le contrôleur ignore complètement si un mail a été envoyé ou un log écrit. Pour ajouter une nouvelle réaction, il vous suffit d'écrire un nouveau listener.
  • Responsabilité unique : chaque listener fait une seule chose, donc les classes restent petites et lisibles.
  • Test facile : vous pouvez vérifier en une seule ligne que l'événement a été dispatché.

Créer des events et des listeners

Vous pouvez générer les deux avec artisan. Lors de la création du listener, le drapeau --event lui indique quel événement écouter, ce qui vous évite un peu de câblage :

php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification --event=OrderShipped

Une classe event ne fait généralement que transporter des données. Passer le modèle concerné au constructeur suffit :

<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, SerializesModels;

    public function __construct(public Order $order) {}
}

Le listener fait le vrai travail dans sa méthode handle(). Le paramètre que vous typez détermine quel événement il écoute :

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use App\Notifications\ShipmentNotification;

class SendShipmentNotification
{
    public function handle(OrderShipped $event): void
    {
        $event->order->user->notify(
            new ShipmentNotification($event->order)
        );
    }
}

Enregistrer les listeners

Dans Laravel 11 et 12, les listeners sont découverts automatiquement. Lorsqu'une classe du dossier app/Listeners type un événement dans sa méthode handle(), le framework fait la liaison pour vous ; aucun enregistrement supplémentaire n'est nécessaire. Cela supprime le besoin du tableau $listen dans l'ancien EventServiceProvider.

Il reste des cas où l'enregistrement manuel est pratique — par exemple un listener rapide sous forme de closure. Vous pouvez en enregistrer un dans la méthode boot() de AppServiceProvider :

use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(function (OrderShipped $event) {
        // une réaction rapide et ponctuelle
    });
}

Dispatcher l'événement

Pour publier un événement, vous utilisez la méthode statique dispatch ou le helper global event(). Les deux font la même chose :

use App\Events\OrderShipped;

// méthode statique
OrderShipped::dispatch($order);

// ou le helper event()
event(new OrderShipped($order));

À l'instant où cette ligne s'exécute, chaque listener abonné à OrderShipped est déclenché un par un. Votre contrôleur ne fait que publier l'événement ; le reste relève des listeners. Un usage typique ressemble à ceci :

public function ship(Order $order)
{
    $order->markAsShipped();

    OrderShipped::dispatch($order);

    return back()->with('status', 'Commande expédiée.');
}

Mettre les listeners en file d'attente

Les tâches lentes comme l'envoi d'e-mail ne doivent pas retarder la requête. Pour déplacer un listener en arrière-plan, il vous suffit d'implémenter l'interface ShouldQueue :

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    public int $tries = 3;
    public int $backoff = 30;

    public function handle(OrderShipped $event): void
    {
        // s'exécute en arrière-plan
    }
}

Le listener est désormais poussé dans une file et traité par un worker. $tries définit le nombre de tentatives et $backoff l'attente (en secondes) entre elles. Pour que la file tourne, vous devez garder un worker actif en arrière-plan :

php artisan queue:work

Vous pouvez aussi ajouter une méthode failed() qui se déclenche lorsque le listener échoue définitivement, où vous pourriez alerter un administrateur ou écrire un log. Un détail important : les listeners en file sérialisent l'objet event, donc grâce au trait SerializesModels seule la clé du modèle est stockée et le modèle est rechargé frais depuis la base de données lorsque le job s'exécute.

Regrouper les listeners avec un event subscriber

Si vous voulez rassembler plusieurs listeners pour un même sujet dans une seule classe, vous pouvez utiliser un subscriber. Un subscriber est une classe qui enregistre ses propres associations d'événements via une méthode subscribe() :

<?php

namespace App\Listeners;

use Illuminate\Events\Dispatcher;

class OrderEventSubscriber
{
    public function handleShipped($event): void { /* ... */ }
    public function handleCancelled($event): void { /* ... */ }

    public function subscribe(Dispatcher $events): array
    {
        return [
            \App\Events\OrderShipped::class => 'handleShipped',
            \App\Events\OrderCancelled::class => 'handleCancelled',
        ];
    }
}

Cette approche est idéale pour garder des événements liés, comme le cycle de vie d'une commande, dans un seul endroit lisible.

Questions fréquentes

Dois-je utiliser un event/listener ou un job ?

Les deux se complètent. Un job dit « fais cette tâche en arrière-plan » ; c'est une tâche unique et précise. Un event dit « ceci s'est produit » et peut déclencher plusieurs réactions indépendantes. S'il n'y a qu'une seule tâche, dispatchez directement un job ; si plusieurs parties doivent réagir à un même fait, utilisez un event/listener (et mettez le listener en file pour profiter aussi de l'avantage du job).

Comment vérifier dans un test qu'un événement a été dispatché ?

Vous simulez les événements avec Event::fake(), puis vous vérifiez avec Event::assertDispatched(OrderShipped::class). Cela vous permet de tester uniquement que l'événement s'est déclenché, sans exécuter les vrais listeners.

Quel est le rapport avec les model events ?

Eloquent déclenche automatiquement ses propres model events comme created, updated et deleted. Les écouter avec une classe observer est souvent plus propre pour le travail lié au cycle de vie du modèle (par exemple générer un slug à la création). Pour des faits plus larges, au niveau métier, écrire vos propres classes event est le meilleur choix.

Si vous voulez rendre votre code faiblement couplé, nous pouvons séparer ensemble la logique surchargée de vos contrôleurs en events et listeners ; contactez-moi pour simplifier votre architecture Laravel.

Bu kategorideki tüm yazılar →

Devamı için