Ein Laravel Event ist der sauberste Weg, anzukündigen, dass in Ihrer Anwendung „etwas passiert ist", während der Code, der darauf reagiert, vollständig getrennt bleibt. Statt drei verschiedene Aufgaben — eine Willkommens-E-Mail senden, einen Logeintrag schreiben und eine Statistik aktualisieren — in einen einzigen Controller zu quetschen, wenn sich ein Benutzer registriert, veröffentlichen Sie ein einziges Event, und die Listener, die sich darauf abonnieren, teilen sich die Arbeit. Das Ergebnis ist eine lose gekoppelte, testbare Codebasis, die offen für Wachstum ist. In diesem Artikel bauen wir die Event/Listener-Architektur von Grund auf auf, stellen sie in die Queue und sehen Schritt für Schritt, wie man sie in realen Szenarien einsetzt.
Was ist die Idee hinter Event und Listener?
Die Idee ist einfach: Ein Event ist ein Nachrichtenobjekt, das „etwas Geschehenes" repräsentiert — zum Beispiel OrderShipped. Ein Listener ist der Code, der auf dieses Event reagiert — etwa dem Kunden eine E-Mail zu schicken. Der Code, der das Event veröffentlicht, muss nicht wissen, wer zuhört. Das ist das klassische Publish/Subscribe-Muster, und es bringt einige wichtige Vorteile:
- Lose Kopplung: Der Controller weiß nicht, ob eine Mail gesendet oder ein Log geschrieben wurde. Um eine neue Reaktion hinzuzufügen, schreiben Sie einfach einen neuen Listener.
- Einzelverantwortung: Jeder Listener erledigt eine Aufgabe, sodass die Klassen klein und lesbar bleiben.
- Einfaches Testen: Sie können in einer einzigen Zeile bestätigen, dass das Event ausgelöst wurde.
Events und Listener erstellen
Sie können beide mit artisan generieren. Beim Erstellen des Listeners teilt das Flag --event mit, welches Event er abhören soll, was Ihnen etwas Verdrahtung erspart:
php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification --event=OrderShipped
Eine Event-Klasse trägt meist nur Daten. Es reicht, das betreffende Modell in den Konstruktor zu übergeben:
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use Dispatchable, SerializesModels;
public function __construct(public Order $order) {}
}
Der Listener erledigt die eigentliche Arbeit in seiner handle()-Methode. Der Parameter, den Sie per Type-Hint angeben, bestimmt, welches Event er abhört:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use App\Notifications\ShipmentNotification;
class SendShipmentNotification
{
public function handle(OrderShipped $event): void
{
$event->order->user->notify(
new ShipmentNotification($event->order)
);
}
}
Listener registrieren
In Laravel 11 und 12 werden Listener automatisch erkannt. Wenn eine Klasse im Ordner app/Listeners in ihrer handle()-Methode ein Event per Type-Hint angibt, verdrahtet das Framework es für Sie; eine zusätzliche Registrierung ist nicht nötig. Das macht das $listen-Array im alten EventServiceProvider überflüssig.
Es gibt dennoch Fälle, in denen eine manuelle Registrierung praktisch ist — zum Beispiel ein schneller Listener als Closure. Sie können einen in der boot()-Methode von AppServiceProvider registrieren:
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(function (OrderShipped $event) {
// eine schnelle, einmalige Reaktion
});
}
Das Event auslösen (dispatch)
Um ein Event zu veröffentlichen, verwenden Sie die statische Methode dispatch oder den globalen Helfer event(). Beide tun dasselbe:
use App\Events\OrderShipped;
// statische Methode
OrderShipped::dispatch($order);
// oder der event()-Helfer
event(new OrderShipped($order));
In dem Moment, in dem diese Zeile läuft, wird jeder Listener, der OrderShipped abonniert hat, einer nach dem anderen ausgelöst. Ihr Controller veröffentlicht nur das Event; der Rest liegt bei den Listenern. Eine typische Verwendung sieht so aus:
public function ship(Order $order)
{
$order->markAsShipped();
OrderShipped::dispatch($order);
return back()->with('status', 'Bestellung versandt.');
}
Listener in die Queue legen
Langsame Arbeit wie das Senden von E-Mails sollte die Anfrage nicht aufhalten. Um einen Listener in den Hintergrund zu verlagern, müssen Sie nur die Schnittstelle ShouldQueue implementieren:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
public int $tries = 3;
public int $backoff = 30;
public function handle(OrderShipped $event): void
{
// läuft im Hintergrund
}
}
Der Listener wird nun in eine Queue geschoben und von einem Worker verarbeitet. $tries legt die Anzahl der Wiederholungen fest und $backoff die Wartezeit (in Sekunden) dazwischen. Damit die Queue läuft, müssen Sie einen Worker im Hintergrund am Leben halten:
php artisan queue:work
Sie können auch eine failed()-Methode hinzufügen, die ausgelöst wird, wenn der Listener endgültig fehlschlägt, wo Sie einen Administrator benachrichtigen oder ein Log schreiben könnten. Ein wichtiges Detail: In die Queue gelegte Listener serialisieren das Event-Objekt, sodass dank des SerializesModels-Traits nur der Schlüssel des Modells gespeichert und das Modell beim Ausführen des Jobs frisch aus der Datenbank neu geladen wird.
Listener mit einem Event Subscriber gruppieren
Wenn Sie mehrere Listener für ein einziges Thema in einer Klasse sammeln möchten, können Sie einen Subscriber verwenden. Ein Subscriber ist eine Klasse, die ihre eigenen Event-Zuordnungen über eine subscribe()-Methode registriert:
<?php
namespace App\Listeners;
use Illuminate\Events\Dispatcher;
class OrderEventSubscriber
{
public function handleShipped($event): void { /* ... */ }
public function handleCancelled($event): void { /* ... */ }
public function subscribe(Dispatcher $events): array
{
return [
\App\Events\OrderShipped::class => 'handleShipped',
\App\Events\OrderCancelled::class => 'handleCancelled',
];
}
}
Dieser Ansatz ist ideal, um zusammengehörige Events, etwa den Lebenszyklus einer Bestellung, an einem einzigen, lesbaren Ort zu halten.
Häufige Fragen
Sollte ich ein Event/Listener oder einen Job verwenden?
Beide ergänzen sich. Ein Job sagt „erledige diese Aufgabe im Hintergrund"; er ist eine einzelne, spezifische Aufgabe. Ein Event sagt „das ist passiert" und kann mehrere unabhängige Reaktionen auslösen. Wenn es nur eine Aufgabe gibt, dispatchen Sie direkt einen Job; wenn mehrere Parteien auf ein einziges Ereignis reagieren müssen, verwenden Sie ein Event/Listener (und legen Sie den Listener in die Queue, um auch den Vorteil des Jobs zu erhalten).
Wie bestätige ich in einem Test, dass ein Event ausgelöst wurde?
Sie faken Events mit Event::fake() und prüfen dann mit Event::assertDispatched(OrderShipped::class). So testen Sie nur, dass das Event ausgelöst wurde, ohne die echten Listener auszuführen.
Wie verhält sich das zu Model Events?
Eloquent löst automatisch eigene Model Events wie created, updated und deleted aus. Diesen mit einer Observer-Klasse zuzuhören, ist für Arbeit, die an den Lebenszyklus des Modells gebunden ist (etwa das Generieren eines Slugs beim Erstellen), oft sauberer. Für umfassendere Ereignisse auf Geschäftsebene ist das Schreiben eigener Event-Klassen die bessere Wahl.
Wenn Sie Ihren Code lose gekoppelt machen möchten, können wir die überladene Logik in Ihren bestehenden Controllern gemeinsam in Events und Listener aufteilen; nehmen Sie Kontakt auf, um Ihre Laravel-Architektur zu vereinfachen.