> ## 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-Listener in Paketen registrieren und cachen

> Analysieren Sie die Event-Registrierung und die Implementierung von event:cache in Laravel 13 und erfahren Sie, wie Pakete Listener explizit registrieren, Doppelregistrierungen mit der automatischen Erkennung vermeiden, Abhängigkeiten auflösen und das Verhalten bei Updates prüfen.

Wenn Sie in Ihrem Paket eventgesteuerte Erweiterungspunkte anbieten möchten, entwerfen Sie die **Bereitstellung der Event-Klassen** und die **Registrierung der Listener** getrennt voneinander. Wenn Ihr Aufbau funktioniert, ohne dass Nutzer Verzeichnisse innerhalb von `vendor` zur automatischen Erkennung hinzufügen müssen, reduzieren Sie die Annahmen bei Installation, Update und Cache-Erstellung.

Diese Seite untersucht die Implementierung in Laravel Framework `v13.36.0` und wendet die grundlegenden Operationen aus [Events](/de/events) auf die langfristige Wartung von Paketen an. Zum Laden von Providern siehe [Paketerkennung](/de/advanced/package-discovery), zur Testumgebung siehe [Pakete testen](/de/advanced/package-testing).

## Listener eines Pakets explizit registrieren

Verarbeitungen, die ein Paket standardmäßig ausführt, können Sie in `boot()` eines regulären `Illuminate\Support\ServiceProvider` mit `Event::listen()` registrieren. Das Standardziel der automatischen Listener-Erkennung in Laravel ist `app/Listeners` der Anwendung; `src/Listeners` innerhalb eines Pakets wird nicht automatisch durchsucht.

Als Beispiel erstellen wir ein Paket, das ein Event für eine abgeschlossene Zustellung protokolliert. Das Event ist lediglich eine Klasse, die die für die Benachrichtigung benötigten Werte enthält.

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

namespace Acme\Courier\Events;

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

Der Listener erhält im Konstruktor einen Logger und verarbeitet das 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,
        ]);
    }
}
```

Im Provider registrieren Sie die Klassennamen des Events und des Listeners.

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

Dieser Aufbau setzt voraus, dass der Provider selbst geladen wird. Stellen Sie `extra.laravel.providers` in Composer oder eine explizite Registrierung durch die Nutzer bereit. Wenn das Event sowohl bei der HTTP-Verarbeitung als auch in Queue-Workern benötigt wird, schränken Sie die Registrierung nicht mit `runningInConsole()` ein.

<Warning>
  Implementieren Sie `DeferrableProvider` nicht allein wegen der Listener-Registrierung in diesem Provider. Wenn die verzögerten Services nicht aufgelöst werden, werden auch die Listener nicht registriert. Lagern Sie Bindings rechenintensiver Services bei Bedarf in einen separaten [deferred Provider](/de/advanced/deferred-provider) aus.
</Warning>

## Registrierung und Instanziierung erfolgen zu unterschiedlichen Zeitpunkten

`Dispatcher::listen()` fügt Listener für einen regulären Event-Namen einem internen Array hinzu. Wenn Sie wie oben einen Klassennamen übergeben, wird an dieser Stelle noch keine Listener-Instanz erzeugt.

Bei synchronen Listenern löst `createClassCallable()` die Klasse nach dem Auslösen des Events über `make()` des Containers auf. Daher können Sie Abhängigkeitsinjektion im Konstruktor nutzen.

```mermaid theme={null}
flowchart TD
    A["boot() des Providers"] --> B["Event::listen() speichert den Klassennamen"]
    B --> C["ShipmentDelivered wird ausgelöst"]
    C --> D["Dispatcher wählt die Listener aus"]
    D --> E["Container löst den synchronen Listener auf"]
    E --> F["Event wird an handle() übergeben"]
```

Auf diesem Weg wird für `handle()` kein `call()` des Containers ausgeführt. Entwerfen Sie Listener so, dass **beliebige Services in den Konstruktor injiziert werden und `handle()` das Event entgegennimmt**. Gehen Sie nicht davon aus, dass es sich um denselben Mechanismus wie bei der [Abhängigkeitsinjektion in `handle()` von Artisan-Befehlen](/de/advanced/package-commands) handelt.

Hier geht es um synchrone Listener. Wenn Sie `ShouldQueue` implementieren, wird ein separater Pfad für Queues verwendet. Prüfen Sie daher zu Transaktionen, Wiederholungsversuchen und Abhängigkeitsauflösung auch [Events in der Queue verarbeiten](/de/events) und [Queues](/de/queues).

## event:cache verhindert keine explizite Registrierung

`event:cache` ist kein Befehl, der sämtliche im Event-Dispatcher registrierten Listener vollständig serialisiert.

`EventCacheCommand::getEvents()` listet `Illuminate\Foundation\Support\Providers\EventServiceProvider` und davon abgeleitete Provider auf und speichert für jeden Provider die Ergebnisse der automatischen Erkennung sowie die Zuordnungen aus `listens()`. `Event::listen()` in `ServiceProvider::boot()` eines regulären Providers gehört nicht zu den gesammelten Einträgen.

Andererseits wird `boot()` regulärer Provider auch bei einem Start mit vorhandenem Cache ausgeführt. **Dass eine Registrierung nicht im Cache enthalten ist, bedeutet nicht, dass sie bei vorhandenem Cache nicht registriert wird.**

| Registrierungsmethode | Was `event:cache` speichert | Beim Start mit vorhandenem Cache |
| - | - | - |
| `Event::listen()` in `boot()` eines regulären Providers | Diese Registrierung selbst wird nicht gesammelt | Wird in `boot()` erneut registriert |
| `$listen` des Foundation-`EventServiceProvider` | Zuordnungen werden pro Provider-Klassenname gespeichert | `getEvents()` liest den Cache |
| Von der automatischen Erkennung erfasste Listener | Erkennungsergebnisse werden in den Zuordnungen gespeichert | Der Cache wird verwendet, statt Verzeichnisse erneut zu durchsuchen |

Ein Paket kann auch vom Foundation-`EventServiceProvider` erben und `$listen` verwenden. In der untersuchten Implementierung bezieht sich der Standardwert von `shouldDiscoverEvents()` jedoch auf die Basisklasse selbst. **Allein durch das Erben wird das Listener-Verzeichnis des Pakets nicht automatisch erkannt.**

Außerdem werden `$subscribe` und `$observers` dieses Providers getrennt von der Schleife verarbeitet, die die Ergebnisse von `getEvents()` registriert. Schließen Sie nicht allein aus dem Cache für `$listen`, dass auch diese Registrierungen übersprungen werden.

<Tip>
  `Event::subscribe(SomeSubscriber::class)` erzeugt den Subscriber bei der Registrierung über den Container und ruft `subscribe()` auf. Das ist nicht so verzögert wie `Event::listen()` mit einem Klassennamen. Vermeiden Sie Seiteneffekte wie externe Kommunikation im Konstruktor oder in `subscribe()` des Subscribers.
</Tip>

## Doppelregistrierung mit der automatischen Erkennung vermeiden

Wenn Nutzer das Listener-Verzeichnis eines Pakets über `withEvents(discover: [...])` hinzufügen und der Provider des Pakets denselben Listener zusätzlich explizit registriert, kann die Verarbeitung bei einem einzigen Event mehrfach ausgeführt werden.

`Dispatcher::listen()` fügt für reguläre Events Einträge zu einem Array hinzu und entfernt Duplikate desselben Klassennamens nicht automatisch für den gesamten Dispatcher. Auch `array_unique()`, das der Foundation-`EventServiceProvider` innerhalb seiner eigenen Zuordnungen anwendet, fasst keine expliziten Registrierungen aus anderen Providern zusammen.

Legen Sie die Verantwortung für die Registrierung auf eine Seite fest.

* Wenn **das Paket die Standard-Listener registriert**, verlangen Sie von Nutzern nicht, dieses Verzeichnis zur automatischen Erkennung hinzuzufügen.
* Wenn **Nutzer die benötigten Listener auswählen**, stellt das Paket die Event- und Listener-Klassen bereit und registriert denselben Listener nicht standardmäßig.
* Wenn Sie von der automatischen Erkennung zur expliziten Registrierung wechseln, weisen Sie darauf hin, dass auch die Erkennungseinstellungen auf Nutzerseite und alte Event-Caches aktualisiert werden müssen.

Die Idempotenz der Geschäftslogik ist ebenfalls wichtig. Verdecken Sie eine verbleibende Doppelregistrierung jedoch nicht allein durch Idempotenz, sondern bereinigen Sie die Registrierungswege.

## Das Verhalten vor und nach Updates prüfen

Um den Registrierungsstatus zu untersuchen, führen Sie `event:list` in einer Testanwendung aus, in der das Paket installiert ist. Stellen Sie sicher, dass der betreffende Listener sowohl ohne als auch mit Cache vorhanden ist.

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

Dies ist ein Vorgehen für eine Testanwendung. Die Ausgabe von `event:list` allein garantiert nicht, dass der Listener seine Verarbeitung abschließt oder nur einmal ausgeführt wird. Fügen Sie zusätzlich Tests hinzu, die tatsächlich Events auslösen.

Als Beispiel registrieren Sie `CourierServiceProvider` im [Basis-TestCase von Testbench](/de/advanced/package-testing) und prüfen, dass der Logger genau einmal aufgerufen wird.

```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()` ist praktisch, um das Auslösen von Events zu prüfen, verhindert aber normalerweise die Ausführung der echten Listener. Da dieser Test Registrierung und Verarbeitung prüft, wird es hier nicht verwendet. Prüfen Sie die Kombination mit der automatischen Erkennung und den Start mit vorhandenem Cache ebenfalls in einer separaten Testanwendung bzw. einer neuen Anwendungsinstanz.

<Warning>
  Wenn Sie `event:cache` nach dem Start ausführen, wird der bereits in diesem Prozess registrierte Dispatcher nicht aus dem Cache neu aufgebaut. Prüfen Sie das Verhalten mit Cache bei einem neuen Start. Damit aktualisierte Listener in lang laufenden Workern wirksam werden, ist außerdem ein Neustart gemäß Ihrem regulären Deployment-Ablauf erforderlich.
</Warning>

## Checkliste für die langfristige Wartung

* Dokumentieren Sie Event-Klassennamen, öffentliche Eigenschaften und die Strategie zur Listener-Registrierung als Vertrag gegenüber den Nutzern.
* Wenn Sie Namen oder Payloads von Events ändern, die Nutzer abonnieren, kommunizieren Sie die Änderung gemäß der [Versionskompatibilitätsverwaltung](/de/advanced/package-versioning).
* Nehmen Sie bei Updates mit Änderungen an der automatischen Erkennung oder an `$listen` die Neuerstellung von `event:cache` in den Deployment-Ablauf auf.
* Unterscheiden Sie zwischen den Standard-Listenern des Pakets und eigenen Listenern der Nutzer, und lassen Sie denselben Standard-Listener nicht doppelt registrieren.
* Führen Sie für jede unterstützte PHP- und Laravel-Version fortlaufend Tests durch, die sicherstellen, dass Listener genau einmal ausgeführt werden.

## Herangezogene Primärquellen

* [Offizielle Laravel-Dokumentation: Event-Registrierung, automatische Erkennung, Cache und Subscriber](https://github.com/laravel/docs/blob/73e61270287e4aaa1cecd77cd66aa6ef4f499828/events.md)
* [Dispatcher: listen, subscribe und createClassCallable](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Events/Dispatcher.php)
* [Foundation EventServiceProvider: register, getEvents und shouldDiscoverEvents](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Support/Providers/EventServiceProvider.php)
* [EventCacheCommand: Sammeln der zu cachenden Einträge](https://github.com/laravel/framework/blob/v13.36.0/src/Illuminate/Foundation/Console/EventCacheCommand.php)


## Related topics

- [Weiterführende Themen](/de/advanced/index.md)
- [Paket-Routen registrieren und cachen](/de/advanced/package-routes.md)
- [Paketkonfiguration zusammenführen und cachen](/de/advanced/package-config-merging.md)
- [Laravel-Paketentwicklung](/de/advanced/package-development.md)
- [Eloquent-Observer und Modell-Events](/de/advanced/eloquent-observers.md)


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