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

# Events en listeners

> Leer hoe je met het eventsysteem van Laravel de componenten van je applicatie losjes gekoppeld houdt.

## Wat zijn events?

Het eventsysteem van Laravel is een eenvoudige implementatie van het observer-patroon.
Door gebeurtenissen (events) die in je applicatie plaatsvinden af te vuren en listeners te definiëren die erop reageren, houd je de afhankelijkheden tussen componenten minimaal.

Als je bijvoorbeeld het event "bestelling bevestigd" afvuurt, gaan meerdere listeners onafhankelijk van elkaar aan de slag: "bevestigingsmail sturen", "voorraad verlagen", "notificatie naar Slack sturen".
De code voor het verwerken van de bestelling hoeft niets te weten van de implementatie van het versturen van mails of Slack-notificaties.

<Info>
  Eventklassen plaats je in de map `app/Events`, listenerklassen in de map `app/Listeners`.
  Als een van beide mappen niet bestaat, maakt het Artisan-commando ze automatisch aan.
</Info>

```mermaid theme={null}
flowchart TD
    A["Event afvuren<br>dispatch()"] --> B["Eventdispatcher"]
    B --> C["Listener 1<br>(synchroon)"]
    B --> D["Listener 2<br>(synchroon)"]
    B --> E["Listener 3<br>implementeert ShouldQueue"]
    C --> F["Direct uitgevoerd"]
    D --> G["Direct uitgevoerd"]
    E --> H["Op de queue geplaatst"]
    H --> I["Worker voert asynchroon uit"]
```

## Events en listeners genereren

Met de Artisan-commando's `make:event` en `make:listener` genereer je klassjablonen.

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

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

Voer je ze zonder argumenten uit, dan wordt er interactief om invoer gevraagd.

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

php artisan make:listener
```

## Events registreren

### Event discovery (automatische detectie)

Standaard scant Laravel de map `app/Listeners` en registreert het listeners automatisch.
De mapping met events wordt afgeleid uit het argumenttype van methoden met de naam `handle` of `__invoke`.

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

class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        // Stuur de welkomstmail
    }
}
```

Met PHP-uniontypen kun je meerdere events in één methode ontvangen.

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

Plaats je listeners in een andere map, geef dan extra scanlocaties op in `bootstrap/app.php`.

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

Met wildcards kun je meerdere mappen in één keer opgeven.

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

Met het volgende commando bekijk je de lijst met geregistreerde listeners.

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

<Tip>
  In productie verbeter je de prestaties door het listener-manifest te cachen.
  Voer bij het deployen `php artisan optimize` of `php artisan event:cache` uit.
  Gebruik `php artisan event:clear` om de cache te verwijderen.
</Tip>

### Handmatige registratie

Je kunt listeners ook handmatig registreren met de `Event`-facade in de `boot`-methode van je `AppServiceProvider`.

```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,
    );
}
```

Registreren met een closure is ook mogelijk.

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

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

## Events definiëren

Een eventklasse is een datacontainer. Hij bevat geen logica, maar bewaart de informatie die bij het event hoort als properties.

```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,
    ) {}
}
```

Dankzij de `SerializesModels`-trait worden Eloquent-modellen correct behandeld wanneer gequeuede listeners het event serialiseren.

## Events afvuren

Vuur events af met de statische `dispatch`-methode of de `event()`-helper.

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

// Statische methode
UserRegistered::dispatch($user);

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

Er zijn ook methoden om voorwaardelijk af te vuren.

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

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

### Afvuren na een databasetransactie

Wil je een event pas afvuren nadat een transactie is gecommit, implementeer dan de interface `ShouldDispatchAfterCommit` op de eventklasse.
Als de transactie mislukt, wordt het event weggegooid.

```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,
    ) {}
}
```

## Listeners implementeren

Een listener ontvangt het event in de `handle`-methode.
In de constructor injecteert de servicecontainer automatisch de afhankelijkheden.

```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));
    }
}
```

Als de `handle`-methode van een listener `false` teruggeeft, wordt de doorgifte van het event aan volgende listeners gestopt.

## Gequeuede listeners

Tijdrovende taken zoals het versturen van e-mail of HTTP-requests kun je asynchroon uitvoeren als gequeuede listeners.
Door simpelweg de `ShouldQueue`-interface te implementeren, wordt de listener bij het afvuren van het event automatisch op de queue geplaatst.

```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
    {
        // Deze taak wordt asynchroon uitgevoerd door een queue-worker
    }
}
```

<Info>
  Voordat je gequeuede listeners gebruikt, moet je de queue configureren en een worker starten.
  Zie de pagina [Queues en jobs](/nl/queues) voor details.
</Info>

### Event-listeners debouncen

Wanneer hetzelfde soort event in korte tijd meerdere keren wordt afgevuurd, wil je soms alleen het laatste event verwerken. Door het `DebounceFor`-attribuut aan een gequeuede listener toe te voegen, kun je events binnen de opgegeven periode bundelen.

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

namespace App\Listeners;

use App\Events\ProductUpdated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30)]
class UpdateProductSearchIndex implements ShouldQueue
{
    public function handle(ProductUpdated $event): void
    {
        // Werk de zoekindex van het product bij
    }

    public function debounceId(ProductUpdated $event): string
    {
        return (string) $event->product->getKey();
    }
}
```

In dit voorbeeld wordt, wanneer het `ProductUpdated`-event van hetzelfde product binnen 30 seconden herhaaldelijk wordt afgevuurd, de listener gedebounced en alleen het laatste event verwerkt. Events met een verschillende `debounceId` worden elk afzonderlijk verwerkt.

Om een bovengrens te stellen aan de tijd dat vaak afgevuurde events de listener kunnen blijven uitstellen, geef je `maxWait` op.

```php theme={null}
#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
    // ...
}
```

De cachestore die voor het bijhouden van het debouncen wordt gebruikt, kun je wijzigen met de `debounceVia`-methode die het event ontvangt.

```php theme={null}
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

public function debounceVia(ProductUpdated $event): Repository
{
    return Cache::driver('redis');
}
```

Gedebouncede listeners en unieke listeners kunnen niet samen worden gebruikt. Implementeer geen `ShouldBeUnique` op listeners die het `DebounceFor`-attribuut gebruiken. Als je events afvuurt vanaf meerdere webservers of containers, configureer dan alle servers zo dat ze dezelfde gedeelde cacheserver gebruiken.

### De queueverbinding, -naam en vertraging aanpassen

Met PHP-attributen kun je de verbinding, queuenaam en vertragingstijd instellen.

```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
    {
        // ...
    }
}
```

Je kunt de waarden ook dynamisch bepalen met methoden.

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

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

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

### Maximaal aantal pogingen en time-out

Met de attributen `#[Tries]` en `#[Timeout]` beheers je het gedrag bij falen.

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

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

### Afhandeling bij falen

Als je een `failed`-methode definieert, kun je nazorg beschrijven voor wanneer de listener het maximale aantal pogingen heeft overschreden.

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

public function failed(UserRegistered $event, Throwable $exception): void
{
    // Notificatie naar de beheerder, enz.
}
```

## Eventsubscribers

Met eventsubscribers bundel je meerdere gerelateerde eventhandlers in één klasse.

### Een subscriber schrijven

In de `subscribe`-methode geef je de mapping van events naar handlers terug als array.

```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
    {
        // Loginafhandeling
    }

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

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

### Subscribers registreren

Als event discovery ingeschakeld is, worden subscribers die een array teruggeven vanuit de `subscribe`-methode automatisch geregistreerd.
Wil je handmatig registreren, roep dan `Event::subscribe` aan in de `boot`-methode van je `AppServiceProvider`.

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

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

## Praktijkvoorbeeld: een welkomstmail sturen bij gebruikersregistratie

<Steps>
  <Step title="Maak de eventklasse aan">
    ```bash theme={null}
    php artisan make:event UserRegistered
    ```

    Bewerk `app/Events/UserRegistered.php` en voeg een property toe die de geregistreerde gebruiker bewaart.

    ```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="Maak de listenerklasse aan">
    ```bash theme={null}
    php artisan make:listener SendWelcomeEmail --event=UserRegistered
    ```

    Implementeer `ShouldQueue` om het versturen van de mail asynchroon via de queue te doen.

    ```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="Vuur het event af in de controller">
    Roep na de gebruikersregistratie `UserRegistered::dispatch()` aan.

    ```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');
        }
    }
    ```

    De `RegisterController` vuurt alleen het `UserRegistered`-event af en weet niets van de implementatie van het versturen van de mail.
    Als er later "bij registratie ook een Slack-notificatie sturen" bij komt, hoeft er niets aan de controller te veranderen.
  </Step>

  <Step title="Start een worker">
    Start een worker om de gequeuede listeners te verwerken.

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

<Tip>
  Als event discovery ingeschakeld is, is handmatige registratie in de `AppServiceProvider` niet nodig.
  Listeners in de map `app/Listeners` worden automatisch gedetecteerd.
</Tip>

<Warning>
  Met `php artisan event:list` bekijk je de lijst met geregistreerde events en listeners.
  Controleer regelmatig of er geen onverwachte listeners geregistreerd zijn.
</Warning>


## Related topics

- [Praktische technieken voor Laravel Telescope](/nl/blog/telescope-introduction.md)
- [FAQ over de nieuwe appstructuur van Laravel 11+](/nl/advanced/app-structure-faq.md)
- [Laravel Reverb](/nl/reverb.md)
- [Broadcasting](/nl/broadcasting.md)
- [Events](/nl/packages/laravel-copilot-sdk/events.md)
