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

# Event listeners van een package registreren en cachen

> Lees de implementatie van eventregistratie en event:cache in Laravel 13 en ontdek hoe je listeners in een package expliciet registreert, dubbele registratie met automatische detectie voorkomt, afhankelijkheden resolvet en het gedrag bij updates controleert.

Wil je in je package uitbreidingspunten aanbieden op basis van events, ontwerp dan **het aanbieden van eventklassen** en **het registreren van listeners** als afzonderlijke onderdelen. Een opzet die werkt zonder dat gebruikers mappen binnen `vendor` aan de automatische detectie hoeven toe te voegen, vermindert de aannames bij installatie, updates en het aanmaken van de cache.

Deze pagina bekijkt de implementatie van Laravel Framework `v13.36.0` en past de basisbewerkingen van [Events](/nl/events) toe op het onderhoud van een package op lange termijn. Voor het laden van providers zie je [Package discovery](/nl/advanced/package-discovery), en voor de testomgeving [Packages testen](/nl/advanced/package-testing).

## Listeners van een package expliciet registreren

Verwerking die je package standaard uitvoert, kun je registreren met `Event::listen()` in de `boot()` van een gewone `Illuminate\Support\ServiceProvider`. De standaardlocatie voor automatische detectie van listeners in Laravel is `app/Listeners` van de applicatie; `src/Listeners` binnen een package wordt niet automatisch doorzocht.

Als voorbeeld maken we een package dat een event voor een afgeronde levering logt. Het event is een klasse die alleen de waarden bevat die nodig zijn voor de melding.

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

namespace Acme\Courier\Events;

final class ShipmentDelivered
{
    public function __construct(public readonly string $shipmentId)
    {
    }
}
```

De listener ontvangt een logger in de constructor en verwerkt het event in `handle()`.

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

namespace Acme\Courier\Listeners;

use Acme\Courier\Events\ShipmentDelivered;
use Psr\Log\LoggerInterface;

final class LogDeliveredShipment
{
    public function __construct(private readonly LoggerInterface $logger)
    {
    }

    public function handle(ShipmentDelivered $event): void
    {
        $this->logger->info('Shipment delivered', [
            'shipment_id' => $event->shipmentId,
        ]);
    }
}
```

In de provider registreer je de klassenamen van het event en de listener.

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

namespace Acme\Courier;

use Acme\Courier\Events\ShipmentDelivered;
use Acme\Courier\Listeners\LogDeliveredShipment;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(
            ShipmentDelivered::class,
            LogDeliveredShipment::class,
        );
    }
}
```

Deze opzet gaat ervan uit dat de provider zelf wordt geladen. Zorg voor `extra.laravel.providers` in Composer of voor een expliciete registratie door de gebruiker. Is het event zowel bij HTTP-verwerking als in queue workers nodig, beperk de registratie dan niet met `runningInConsole()`.

<Warning>
  Implementeer `DeferrableProvider` niet op deze provider alleen vanwege het registreren van listeners. Als de uitgestelde services niet worden geresolved, worden de listeners ook niet geregistreerd. Zware service-bindings kun je indien nodig onderbrengen in een aparte [uitgestelde provider](/nl/advanced/deferred-provider).
</Warning>

## Registratie en instantiëring gebeuren op verschillende momenten

`Dispatcher::listen()` voegt de listener voor een gewone eventnaam toe aan een interne array. Geef je zoals hierboven een klassenaam door, dan wordt de listener op dat moment nog niet geïnstantieerd.

Bij synchrone listeners resolvet `createClassCallable()` de klasse na het dispatchen van het event via `make()` van de container. Daardoor kun je dependency injection in de constructor gebruiken.

```mermaid theme={null}
flowchart TD
    A["boot() van de provider"] --> B["Klassenaam opslaan met Event::listen()"]
    B --> C["ShipmentDelivered dispatchen"]
    C --> D["Dispatcher kiest de listeners"]
    D --> E["Synchrone listener resolven via de container"]
    E --> F["Event doorgeven aan handle()"]
```

Deze route voert geen `call()` van de container uit op `handle()`. Ontwerp de listener zo dat **willekeurige services in de constructor worden geïnjecteerd en `handle()` het event ontvangt**. Ga er niet van uit dat dit hetzelfde mechanisme is als [dependency injection in `handle()` van Artisan-commands](/nl/advanced/package-commands).

Wat hier wordt beschreven, geldt voor synchrone listeners. Implementeer je `ShouldQueue`, dan volgt de listener een aparte route voor de queue. Bekijk in dat geval ook [listeners in de queue](/nl/events) en [Queues](/nl/queues) voor transacties, nieuwe pogingen en het resolven van afhankelijkheden.

## event:cache stopt expliciete registratie niet

`event:cache` is geen command dat alle bij de event-dispatcher geregistreerde listeners in hun geheel serialiseert.

`EventCacheCommand::getEvents()` somt `Illuminate\Foundation\Support\Providers\EventServiceProvider` en de daarvan afgeleide providers op, en slaat per provider het resultaat van de automatische detectie en de mapping uit `listens()` op. `Event::listen()` in een gewone `ServiceProvider::boot()` valt niet onder deze verzameling.

Tegelijkertijd wordt `boot()` van gewone providers ook uitgevoerd bij een start waarbij een cache aanwezig is. **Niet in de cache staan is iets anders dan niet geregistreerd worden wanneer er een cache is.**

| Registratiemethode | Wat `event:cache` opslaat | Bij een start met cache |
| - | - | - |
| `Event::listen()` in `boot()` van een gewone provider | Deze registratie valt zelf buiten de verzameling | Wordt opnieuw geregistreerd in `boot()` |
| `$listen` van de Foundation-`EventServiceProvider` | Mapping per klassenaam van de provider | `getEvents()` leest de cache |
| Listeners die onder de automatische detectie vallen | Detectieresultaat in de mapping | De cache wordt gebruikt in plaats van de map opnieuw te doorzoeken |

Je kunt ook `$listen` gebruiken in een package dat de Foundation-`EventServiceProvider` uitbreidt. In de bekeken implementatie heeft de standaardwaarde van `shouldDiscoverEvents()` echter betrekking op de basisklasse zelf. **Alleen het uitbreiden ervan zorgt er niet voor dat de listenermap van je package automatisch wordt gedetecteerd.**

Daarnaast worden `$subscribe` en `$observers` van deze provider afzonderlijk verwerkt, los van de lus die het resultaat van `getEvents()` registreert. Concludeer dus niet op basis van alleen de cache van `$listen` dat ook deze registraties worden overgeslagen.

<Tip>
  `Event::subscribe(SomeSubscriber::class)` maakt bij de registratie de subscriber aan via de container en roept `subscribe()` aan. Dit is niet even lui als `Event::listen()` met een klassenaam. Vermijd bijwerkingen zoals externe communicatie in de constructor van de subscriber en in `subscribe()`.
</Tip>

## Dubbele registratie met automatische detectie voorkomen

Voegt de gebruiker de listenermap van het package toe met `withEvents(discover: [...])` en registreert de provider van het package dezelfde listener ook expliciet, dan kan de verwerking voor één event meerdere keren draaien.

`Dispatcher::listen()` voegt voor gewone events elementen toe aan een array en ontdubbelt dezelfde klassenaam niet automatisch over de hele dispatcher. De `array_unique()` die de Foundation-`EventServiceProvider` binnen zijn eigen mapping uitvoert, voegt ook geen expliciete registraties van andere providers samen.

Leg de verantwoordelijkheid voor de registratie bij één partij.

* **Registreert het package de standaardlisteners**, vraag gebruikers dan niet om die map aan de automatische detectie toe te voegen.
* **Kiest de gebruiker de benodigde listeners**, dan biedt het package de event- en listenerklassen aan en registreert het dezelfde listeners niet standaard.
* Stap je over van automatische detectie naar expliciete registratie, vermeld dan dat ook de detectie-instellingen aan de kant van de gebruiker en een oude eventcache moeten worden bijgewerkt.

Idempotentie van de bedrijfslogica is ook belangrijk, maar verberg een dubbele registratie niet alleen met idempotentie: ruim de registratieroutes op.

## Gedrag voor en na een update controleren

Om de registratiestatus te bekijken, voer je `event:list` uit in een testapplicatie waarin het package is geïnstalleerd. Controleer dat de betreffende listeners zowel zonder als met cache aanwezig zijn.

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

Dit is een procedure voor een testapplicatie. Alleen de uitvoer van `event:list` garandeert niet dat de listener zijn verwerking voltooit of maar één keer wordt uitgevoerd. Voeg daarom ook tests toe die een event echt dispatchen.

Als voorbeeld registreer je `CourierServiceProvider` in de [basis-TestCase van Testbench](/nl/advanced/package-testing) en controleer je dat de logger precies één keer wordt aangeroepen.

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

namespace Acme\Courier\Tests\Feature;

use Acme\Courier\Events\ShipmentDelivered;
use Acme\Courier\Tests\TestCase;
use Illuminate\Support\Facades\Event;
use Psr\Log\LoggerInterface;

class PackageEventsTest extends TestCase
{
    public function test_delivery_is_logged_once(): void
    {
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())
            ->method('info')
            ->with('Shipment delivered', ['shipment_id' => 'shipment-123']);

        $this->app->instance(LoggerInterface::class, $logger);

        Event::dispatch(new ShipmentDelivered('shipment-123'));
    }
}
```

`Event::fake()` is handig om te controleren of een event is gedispatcht, maar stopt normaal gesproken de uitvoering van de echte listeners. Omdat deze test registratie en verwerking controleert, wordt het hier niet gebruikt. Controleer de combinatie met automatische detectie en een start met cache ook in een aparte testapplicatie en met een nieuwe applicatie-instantie.

<Warning>
  Voer je `event:cache` uit na het opstarten, dan wordt de al geregistreerde dispatcher binnen dat proces niet opnieuw opgebouwd vanuit de cache. Controleer het gedrag met cache bij een nieuwe start. Om bijgewerkte listeners door te voeren in langlopende workers is ook een herstart volgens je gebruikelijke deploymentprocedure nodig.
</Warning>

## Checklist voor onderhoud op lange termijn

* Documenteer eventklassenamen, publieke properties en het registratiebeleid voor listeners als contract voor gebruikers.
* Wijzig je de naam of payload van events waarop gebruikers zich abonneren, communiceer de wijziging dan volgens [Versiecompatibiliteit beheren](/nl/advanced/package-versioning).
* Neem bij updates die de automatische detectie of `$listen` wijzigen het opnieuw aanmaken van `event:cache` op in de deploymentprocedure.
* Maak onderscheid tussen de standaardlisteners van het package en eigen listeners van gebruikers, en laat dezelfde standaardlistener niet dubbel registreren.
* Blijf voor elke ondersteunde PHP- en Laravel-versie testen dat listeners precies één keer worden uitgevoerd.

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: registratie, automatische detectie, cache en subscribers van events](https://github.com/laravel/docs/blob/73e61270287e4aaa1cecd77cd66aa6ef4f499828/events.md)
* [Dispatcher: listen, subscribe, createClassCallable](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Events/Dispatcher.php)
* [Foundation EventServiceProvider: register, getEvents, shouldDiscoverEvents](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php)
* [EventCacheCommand: verzamelen van wat in de cache komt](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Console/EventCacheCommand.php)


## Related topics

- [Geavanceerde onderwerpen](/nl/advanced/index.md)
- [Routes van packages registreren en cachen](/nl/advanced/package-routes.md)
- [Artisan-commands van een package registreren en afhankelijkheden resolven](/nl/advanced/package-commands.md)
- [Events en listeners](/nl/events.md)
- [Laravel-packages ontwikkelen](/nl/advanced/package-development.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.