Skip to content

Les événements

Quand quelque chose d'important se produit dans le domaine — une commande est passée, un statut change, un compte est créé — le cœur émet un événement. Vous y réagissez par un listener, sans modifier le code qui l'émet.

Le problème

À la validation d'une commande, il faut envoyer un e-mail de confirmation, décrémenter le stock, notifier un ERP… Ces réactions appartiennent à des packages différents, et le cœur ne doit en connaître aucune. L'événement est le point de découplage.

Le principe

Le cœur émet ; plusieurs listeners écoutent, indépendamment.

Les événements de domaine

ÉvénementQuand
OrderPlacedcommande créée et persistée
OrderStatusChangedchangement de statut de commande
CartItemAdded / CartItemUpdated / CartItemRemovedmodification du panier
CartMergedfusion panier invité → compte
UserRegistered / UserEmailChangedcycle de vie du compte
CurrencyChangedchangement de devise
ReindexRequesteddemande de réindexation produit
CheckoutStarted / CheckoutStepCompleted / CheckoutCompletedparcours dans le tunnel de commande
ProductViewed / CategoryViewed / SearchPerformedconsultation du catalogue

La liste à jour est dans la référence.

Déclarés par le cœur, émis par le thème

Les événements de parcours (tunnel, consultation) sont déclarés par le framework bien qu'émis par le thème — comme les noms de routes et les slots. Un package de mesure ou de recommandation s'y branche sans dépendre d'un thème particulier.

Empêcher une action : les événements annulables

Les événements ci-dessus constatent (participe passé : CartItemAdded, OrderPlaced). D'autres annoncent une intention avant qu'elle ne soit exécutée (participe présent) : un écouteur peut alors s'y opposer. C'est le seul moyen d'empêcher une action du cœur sans remplacer le service qui la porte.

ÉvénementRefus
CartItemAdding / CartItemUpdating / CartItemRemovingla méthode du CartService rend false, le panier est inchangé
OrderPlacingActionCancelled est levée avant la transaction — rien n'est écrit
OrderStatusChangingActionCancelled est levée, le statut ne change pas
php
use Slab\Framework\Core\Cart\Events\CartItemAdding;

Event::listen(CartItemAdding::class, function (CartItemAdding $event): void {
    if ($event->product->stock < $event->quantity) {
        $event->cancel(__('Stock insuffisant.'));
    }
});

Le motif du premier écouteur qui s'oppose est conservé : c'est lui qui remonte à l'appelant, et qui porte le message de l'exception Slab\Framework\Core\Event\ActionCancelled (laquelle expose aussi l'événement refusé, via $exception->event).

Pour émettre un tel événement dans votre propre package, faites-lui implémenter CancellableEvent et utilisez le trait Cancellable : sa méthode statique propose(...) construit l'événement, le donne aux écouteurs et le rend — là où dispatch() rend les réponses des écouteurs, seul l'événement porte le veto.

php
$event = MonIntention::propose($contexte);

if ($event->isCancelled()) {
    return false; // ou throw new ActionCancelled($event);
}

Écouter un événement

Dans un package, l'écoute se déclare explicitement (un provider vendor n'a pas l'auto-découverte des listeners). Le plus simple, au boot() :

php
use Illuminate\Support\Facades\Event;
use Slab\Framework\Core\Order\Events\OrderPlaced;

Event::listen(OrderPlaced::class, function (OrderPlaced $event) {
    // $event->order : la commande figée
    NotifierErp::dispatch($event->order); // déléguez à un Job pour ne pas bloquer la requête
});

Pour une vraie feature, préférez un EventServiceProvider dédié avec un listener dédié (queueable) — c'est ce que fait le cœur pour l'e-mail de statut de commande.

Émettre vos propres événements

Rien ne vous empêche de définir et d'émettre vos événements dans votre package (MonEvent::dispatch(...)) pour que d'autres parties s'y branchent. En revanche, les événements du cœur restent émis par le cœur : vous les écoutez, vous ne les déclenchez pas à sa place.

Voir aussi