> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Événements et listeners

> Utilisez le système d'événements de Laravel pour découpler les composants de votre application.

## Qu'est-ce qu'un événement

Le système d'événements de Laravel implémente le pattern observateur.
Vous déclenchez des « événements » représentant des faits survenus dans l'application, et des listeners y réagissent, ce qui minimise les dépendances entre composants.

Par exemple, un événement « commande validée » peut déclencher indépendamment « envoyer l'e-mail de confirmation », « décrémenter le stock » et « notifier Slack ».
Le code de la commande ignore complètement l'implémentation de l'envoi de mails ou de Slack.

<Info>
  Les classes d'événement vont dans `app/Events`, les listeners dans `app/Listeners`.
  Ces dossiers sont créés automatiquement par les commandes Artisan.
</Info>

```mermaid theme={null}
flowchart TD
    A["Dispatch de l'événement<br>dispatch()"] --> B["Dispatcher"]
    B --> C["Listener 1<br>(synchrone)"]
    B --> D["Listener 2<br>(synchrone)"]
    B --> E["Listener 3<br>ShouldQueue"]
    C --> F["Exécution immédiate"]
    D --> G["Exécution immédiate"]
    E --> H["Mise en file"]
    H --> I["Exécution asynchrone via un worker"]
```

## Générer événements et listeners

Utilisez `make:event` et `make:listener`.

```bash theme={null}
php artisan make:event UserRegistered

php artisan make:listener SendWelcomeEmail --event=UserRegistered
```

Sans arguments, la commande est interactive.

```bash theme={null}
php artisan make:event

php artisan make:listener
```

## Enregistrement des listeners

### Auto-discovery

Par défaut, Laravel scanne `app/Listeners` et enregistre les listeners.
La classe/méthode `handle` ou `__invoke` est inspectée : le type de son argument détermine l'événement écouté.

```php theme={null}
use App\Events\UserRegistered;

class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        // Envoi de l'e-mail de bienvenue
    }
}
```

Un type union PHP permet de gérer plusieurs événements dans la même méthode.

```php theme={null}
public function handle(UserRegistered|UserUpdated $event): void
{
    // ...
}
```

Pour des listeners hors de ce dossier, ajoutez des chemins de scan dans `bootstrap/app.php`.

```php theme={null}
->withEvents(discover: [
    __DIR__.'/../app/Domain/Orders/Listeners',
])
```

Wildcards autorisés :

```php theme={null}
->withEvents(discover: [
    __DIR__.'/../app/Domain/*/Listeners',
])
```

Liste des listeners enregistrés :

```bash theme={null}
php artisan event:list
```

<Tip>
  En production, mettez le manifest en cache : `php artisan optimize` ou `php artisan event:cache`. Videz-le avec `php artisan event:clear`.
</Tip>

### Enregistrement manuel

Via `Event` dans `AppServiceProvider::boot`.

```php theme={null}
use App\Events\UserRegistered;
use App\Listeners\SendWelcomeEmail;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(
        UserRegistered::class,
        SendWelcomeEmail::class,
    );
}
```

Ou via closure.

```php theme={null}
use App\Events\UserRegistered;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(function (UserRegistered $event) {
        // ...
    });
}
```

## Définir un événement

Une classe d'événement n'est qu'un conteneur de données. Elle porte les informations liées à l'événement, sans logique.

```php theme={null}
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}
```

Le trait `SerializesModels` garantit une bonne sérialisation des modèles Eloquent quand le listener est en file.

## Déclencher un événement

Utilisez la méthode statique `dispatch` ou le helper `event()`.

```php theme={null}
use App\Events\UserRegistered;

// Statique
UserRegistered::dispatch($user);

// Helper
event(new UserRegistered($user));
```

Il existe aussi les variantes conditionnelles.

```php theme={null}
UserRegistered::dispatchIf($condition, $user);

UserRegistered::dispatchUnless($condition, $user);
```

### Après la commit d'une transaction

Pour ne dispatcher qu'après commit d'une transaction, implémentez `ShouldDispatchAfterCommit`. En cas de rollback, l'événement est ignoré.

```php theme={null}
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class UserRegistered implements ShouldDispatchAfterCommit
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public User $user,
    ) {}
}
```

## Implémenter un listener

La méthode `handle` reçoit l'événement.
Le constructeur peut recevoir des dépendances via le conteneur.

```php theme={null}
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail
{
    public function __construct() {}

    public function handle(UserRegistered $event): void
    {
        Mail::to($event->user->email)
            ->send(new WelcomeMail($event->user));
    }
}
```

Retourner `false` depuis `handle` interrompt la propagation aux listeners suivants.

## Listeners en file (queued)

Pour les traitements longs (mail, HTTP…), exécutez le listener en asynchrone.
Implémentez `ShouldQueue` : Laravel enfile automatiquement le listener lors du dispatch.

```php theme={null}
<?php

namespace App\Listeners;

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

class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        // Exécuté en asynchrone par le worker
    }
}
```

<Info>
  Nécessite la configuration de la queue et le démarrage d'un worker. Voir [Queues et jobs](/fr/queues).
</Info>

### Personnaliser connexion, queue et délai

Utilisez les attributs PHP.

```php theme={null}
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('emails')]
#[Delay(10)]
class SendWelcomeEmail implements ShouldQueue
{
    public function handle(UserRegistered $event): void
    {
        // ...
    }
}
```

Ou via des méthodes dynamiques.

```php theme={null}
public function viaConnection(): string
{
    return 'redis';
}

public function viaQueue(): string
{
    return 'emails';
}

public function withDelay(UserRegistered $event): int
{
    return 10;
}
```

### Nombre max d'essais et timeout

```php theme={null}
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(30)]
class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
```

### Gestion des échecs

La méthode `failed` est appelée après épuisement des tentatives.

```php theme={null}
use Throwable;

public function failed(UserRegistered $event, Throwable $exception): void
{
    // Notifier un administrateur, par exemple
}
```

## Souscripteurs d'événements

Un souscripteur regroupe plusieurs handlers dans une même classe.

### Créer un souscripteur

`subscribe` renvoie la table événement → handler.

```php theme={null}
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserActivitySubscriber
{
    public function handleLogin(Login $event): void
    {
        // Login
    }

    public function handleLogout(Logout $event): void
    {
        // Logout
    }

    /**
     * @return array<string, string>
     */
    public function subscribe(Dispatcher $events): array
    {
        return [
            Login::class => 'handleLogin',
            Logout::class => 'handleLogout',
        ];
    }
}
```

### Enregistrement

Avec l'auto-discovery, les souscripteurs retournant un tableau sont enregistrés automatiquement.
Sinon, dans `AppServiceProvider::boot`, appelez `Event::subscribe`.

```php theme={null}
use App\Listeners\UserActivitySubscriber;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::subscribe(UserActivitySubscriber::class);
}
```

## Exemple : e-mail de bienvenue à l'inscription

<Steps>
  <Step title="Créer l'événement">
    ```bash theme={null}
    php artisan make:event UserRegistered
    ```

    Ajoutez une propriété pour l'utilisateur.

    ```php theme={null}
    <?php

    namespace App\Events;

    use App\Models\User;
    use Illuminate\Broadcasting\InteractsWithSockets;
    use Illuminate\Foundation\Events\Dispatchable;
    use Illuminate\Queue\SerializesModels;

    class UserRegistered
    {
        use Dispatchable, InteractsWithSockets, SerializesModels;

        public function __construct(
            public User $user,
        ) {}
    }
    ```
  </Step>

  <Step title="Créer le listener">
    ```bash theme={null}
    php artisan make:listener SendWelcomeEmail --event=UserRegistered
    ```

    Implémentez `ShouldQueue` pour un envoi asynchrone.

    ```php theme={null}
    <?php

    namespace App\Listeners;

    use App\Events\UserRegistered;
    use App\Mail\WelcomeMail;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Support\Facades\Mail;

    class SendWelcomeEmail implements ShouldQueue
    {
        public function handle(UserRegistered $event): void
        {
            Mail::to($event->user->email)
                ->send(new WelcomeMail($event->user));
        }
    }
    ```
  </Step>

  <Step title="Dispatcher l'événement depuis le contrôleur">
    Après la création de l'utilisateur.

    ```php theme={null}
    <?php

    namespace App\Http\Controllers\Auth;

    use App\Events\UserRegistered;
    use App\Models\User;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\Http\Request;

    class RegisterController extends Controller
    {
        public function store(Request $request): RedirectResponse
        {
            $user = User::create($request->validated());

            UserRegistered::dispatch($user);

            return redirect('/dashboard');
        }
    }
    ```

    `RegisterController` ignore l'implémentation de l'envoi de mail.
    Ajouter plus tard un « notifier Slack » ne modifie pas le contrôleur.
  </Step>

  <Step title="Démarrer un worker">
    Pour traiter les listeners en file.

    ```bash theme={null}
    php artisan queue:work
    ```
  </Step>
</Steps>

<Tip>
  Avec l'auto-discovery, aucun enregistrement manuel n'est requis dans `AppServiceProvider`.
</Tip>

<Warning>
  Vérifiez régulièrement `php artisan event:list` pour détecter d'éventuels listeners inattendus.
</Warning>


## Related topics

- [Techniques pratiques de Laravel Telescope](/fr/blog/telescope-introduction.md)
- [FAQ sur la nouvelle structure d'application Laravel 11+](/fr/advanced/app-structure-faq.md)
- [Observateurs Eloquent et événements de modèle](/fr/advanced/eloquent-observers.md)
- [Événements](/fr/packages/laravel-copilot-sdk/events.md)
- [Laravel Telescope](/fr/telescope.md)
