Zum Hauptinhalt springen
Schneespur
Dokumentation durchblättern

Module entwickeln

Events-Referenz

Schneespur löst im Betrieb fachliche Events aus; Module hängen sich daran, um auf Zustandsänderungen zu reagieren, ohne Core-Dateien anzufassen.

Schneespur löst während des Betriebs fachliche Events aus — etwa wenn ein Einsatz abgeschlossen, ein Kunde angelegt oder eine Schicht beendet wird. Module können auf diese Events hören und darauf reagieren, ohne eine einzige Core-Datei zu verändern.

Warum Events statt direkter Eingriffe

Ein Modul, das bei jedem abgeschlossenen Einsatz etwas tun soll, könnte theoretisch den Core-Code an der passenden Stelle ergänzen. Genau das vermeidet das Event-System: Der Core meldet „dieser Einsatz ist fertig”, und Ihr Modul entscheidet selbst, ob und wie es reagiert. Der Core weiß nichts von Ihrem Modul, Ihr Modul greift nicht in den Core ein — dadurch bleiben Updates konfliktfrei, und mehrere Module können unabhängig auf dasselbe Event reagieren.

Auf Events hören

Listener registrieren Sie in der boot()-Methode Ihres ServiceProviders (zum Unterschied zwischen register() und boot() siehe Das ServiceProvider-Muster). Für einfache Fälle genügt eine Closure:

$this->app['events']->listen(JobCompleted::class, function (JobCompleted $event) {
    // React to job completion
    Log::info('Job completed', ['job_id' => $event->job->id]);
});

Sobald die Reaktion umfangreicher wird, lagern Sie die Logik in eine eigene Listener-Klasse aus. Das hält den ServiceProvider übersichtlich und macht den Listener testbar:

$this->app['events']->listen(JobCompleted::class, MyJobCompletedListener::class);

Job-Events

Die Einsatz-Events bilden den Kern des Tagesgeschäfts ab — vom Beginn der Arbeit bis zum Abschluss eines Einsatzes.

JobStarted

Class: App\Events\JobStarted Ausgelöst: wenn ein Einsatz begonnen wird (der Fahrer nimmt die Arbeit auf).

PropertyTypBeschreibung
$jobApp\Models\Jobder gestartete Einsatz

JobCompleted

Class: App\Events\JobCompleted Ausgelöst: wenn ein Einsatz abgeschlossen wird (der Fahrer markiert die Arbeit als erledigt).

PropertyTypBeschreibung
$jobApp\Models\Jobder abgeschlossene Einsatz
$weatherAvailableboolob für diesen Einsatz Wetterdaten geholt wurden
$isWeatherUpdateboolob es sich um eine reine Wetter-Aktualisierung handelt (Standard: false)

Dies ist das am häufigsten genutzte Event. Der Core-Listener SendJobCompletedNotification reagiert darauf und verschickt Benachrichtigungen über die NotificationChannelRegistry. Das ist ein gutes Beispiel dafür, wie der Core selbst nur über dieses Event arbeitet — Ihr Modul hängt sich an derselben Stelle ein.

Kunden-Events

CustomerCreated

Class: App\Events\CustomerCreated Ausgelöst: wenn ein neuer Kunde angelegt wird.

PropertyTypBeschreibung
$customerApp\Models\Customerder neue Kunde

CustomerUpdated

Class: App\Events\Customer\CustomerUpdated Ausgelöst: wenn ein Kundendatensatz geändert wird.

PropertyTypBeschreibung
$customerApp\Models\Customerder geänderte Kunde

CustomerDeleted

Class: App\Events\Customer\CustomerDeleted Ausgelöst: wenn ein Kunde gelöscht wird.

PropertyTypBeschreibung
$customerApp\Models\Customerder gelöschte Kunde

Benutzer-Events

UserCreated

Class: App\Events\User\UserCreated Ausgelöst: wenn ein neuer Benutzer angelegt wird (Verwaltung oder Fahrer).

PropertyTypBeschreibung
$userApp\Models\Userder neue Benutzer

UserLoggedIn

Class: App\Events\User\UserLoggedIn Ausgelöst: wenn sich ein Benutzer anmeldet.

PropertyTypBeschreibung
$userApp\Models\Userder angemeldete Benutzer

UserLoggedOut

Class: App\Events\User\UserLoggedOut Ausgelöst: wenn sich ein Benutzer abmeldet.

PropertyTypBeschreibung
$userApp\Models\Userder abgemeldete Benutzer

Schicht-Events

Die Schicht-Events tragen zwei Properties: die Schicht selbst und den Fahrer. So lässt sich direkt erkennen, wer eine Schicht begonnen oder beendet hat.

WorkShiftStarted

Class: App\Events\Shift\WorkShiftStarted Ausgelöst: wenn ein Fahrer eine Schicht beginnt.

PropertyTypBeschreibung
$workShiftApp\Models\WorkShiftdie begonnene Schicht
$userApp\Models\Userder Fahrer

WorkShiftEnded

Class: App\Events\Shift\WorkShiftEnded Ausgelöst: wenn ein Fahrer eine Schicht beendet.

PropertyTypBeschreibung
$workShiftApp\Models\WorkShiftdie beendete Schicht
$userApp\Models\Userder Fahrer

Modul-Events

Diese beiden Events feuern, wenn ein Modul über die Oberfläche aktiviert oder deaktiviert wird. Sie sind der saubere Ort für einmalige Einrichtungs- oder Aufräumschritte — siehe auch Modul-Lebenszyklus.

ModuleEnabled

Class: App\Events\Module\ModuleEnabled Ausgelöst: wenn ein Modul über die Verwaltungsoberfläche aktiviert wird.

PropertyTypBeschreibung
$moduleApp\Models\Moduledas aktivierte Modul

ModuleDisabled

Class: App\Events\Module\ModuleDisabled Ausgelöst: wenn ein Modul über die Verwaltungsoberfläche deaktiviert wird.

PropertyTypBeschreibung
$moduleApp\Models\Moduledas deaktivierte Modul

Wetter-Events

WeatherSnapshotCreated

Class: App\Events\WeatherSnapshotCreated Ausgelöst: wenn Wetterdaten für einen Einsatz geholt und gespeichert werden.

PropertyTypBeschreibung
$snapshotApp\Models\WeatherSnapshotder neue Wetter-Snapshot

GPS-/Standort-Events

Anders als die einsatzgebundenen Standortdaten, die der Core nur im Rahmen eines laufenden Einsatzes speichert, gibt es ein Event, das bei jedem Standort-Ping eines Fahrers feuert — auch dann, wenn gerade kein Einsatz aktiv ist.

GpsPointReceived

Class: App\Events\GpsPointReceived Ausgelöst: bei jedem gültigen OwnTracks-Standort-Ping — unabhängig davon, ob der Fahrer gerade einen aktiven Einsatz hat. Wird in OwnTracksController::store() ausgelöst.

PropertyTypBeschreibung
$userApp\Models\Userder Fahrer, der den Ping gesendet hat
$latfloatgeografische Breite
$lonfloatgeografische Länge
$timestampintUnix-Zeitstempel des Standorts
$accuracy?intgemeldete Genauigkeit in Metern (nullable)
$activeJob?App\Models\Jobder aktive Einsatz des Fahrers oder null, wenn gerade keiner läuft

Hinzugekommen in 1.1.6. Der entscheidende Unterschied zu den einsatzgebundenen Standortdaten: Dieses Event feuert bei jedem Ping, also auch im Leerlauf ohne aktiven Einsatz. Damit kommt es deutlich häufiger als die Einsatz-Events — bedenken Sie das, wenn Ihr Listener pro Ping Arbeit verrichtet; halten Sie die Reaktion entsprechend schlank.

Wichtig zur Datenhaltung: Leerlauf-Pings (ohne aktiven Einsatz) werden vom Core nicht gespeichert — die GpsPoint-Ablage bleibt einsatzgebunden. Dieses Event ist also die einzige Stelle, an der Leerlauf-Pings überhaupt sichtbar werden.

Der Hook existiert, damit ein Geofencing-Modul die Live-Position eines Fahrers schon beobachten kann, bevor ein Einsatz existiert, und bei Bedarf selbst einen Einsatz startet (etwa über den JobLifecycleService). Ein solches Geofencing-Modul ist geplant und derzeit noch nicht verfügbar — das Event selbst steht jedoch bereits zur Verfügung.

$this->app['events']->listen(GpsPointReceived::class, function (GpsPointReceived $event) {
    if ($event->activeJob !== null) {
        return; // Fahrer arbeitet bereits an einem Einsatz
    }
    // Prüfen, ob ($event->lat, $event->lon) einen Geofence betreten hat, und ggf. Einsatz starten ...
});

Gängige Muster

Alle abgeschlossenen Einsätze protokollieren

Ein häufiger Anwendungsfall: ein Modul, das jeden abgeschlossenen Einsatz in einem eigenen Protokoll festhält. Über $event->job greifen Sie auf den vollständigen Einsatz und seine Beziehungen zu:

$this->app['events']->listen(JobCompleted::class, function (JobCompleted $event) {
    ModuleLogger::make()->info('my-module', 'Job completed', [
        'job_id' => $event->job->id,
        'customer' => $event->job->customer?->name,
        'type' => $event->job->type->value,
        'weather' => $event->weatherAvailable,
    ]);
});

Benachrichtigung bei Schichtbeginn

$this->app['events']->listen(WorkShiftStarted::class, function (WorkShiftStarted $event) {
    // Notify admin that a driver has started their shift
    $this->sendShiftAlert($event->user, $event->workShift);
});

Auf den Modul-Lebenszyklus reagieren

Wenn Ihr Modul beim Aktivieren einen einmaligen Einrichtungsschritt braucht, prüfen Sie im Listener den Slug — so reagiert das Modul nur auf seine eigene Aktivierung und nicht auf die anderer Module:

$this->app['events']->listen(ModuleEnabled::class, function (ModuleEnabled $event) {
    if ($event->module->slug === 'my-module') {
        // Perform post-activation setup
        $this->runSetup();
    }
});