Dokumentation durchblättern
Module entwickeln
Filter-Hooks
Die FilterRegistry erlaubt Modulen, Daten an festen Punkten im Core zu verändern — mit Prioritäten, Fehlerschutz und eigenen Hooks.
Die FilterRegistry ist ein Hook-System im Stil von WordPress. Module registrieren
Callbacks auf benannten Hooks; der Core wendet sie an klar definierten Stellen an und lässt
Daten durch sie hindurchfließen, während sie verändert werden. So greift ein Modul in den
Datenfluss ein, ohne eine einzige Core-Datei anzufassen.
Wie Filter arbeiten
Ein Filter besteht aus zwei Seiten: Ein Modul registriert einen Callback auf einem Hook, und der Core wendet diesen Hook auf einen Wert an. Der Callback bekommt den Wert herein, verändert ihn und gibt ihn zurück.
// Module registers a filter
$filters = app(FilterRegistry::class);
$filters->register('schneespur.navigation.items', function (array $items): array {
// Modify and return
$items['custom-group'][] = [...];
return $items;
}, 150); // priority 150
// Core applies the filter
$result = $filters->apply('schneespur.navigation.items', $grouped);
Warum so: Der Core kennt Ihr Modul nicht und ruft Ihren Callback nie direkt auf. Er
ruft nur apply() mit einem Wert auf. Welche Module sich auf diesem Hook eingetragen
haben, entscheidet zur Laufzeit die Registry. Dadurch bleiben Core und Modul vollständig
entkoppelt.
Priorität
Mehrere Module können auf demselben Hook sitzen. Die Reihenfolge steuert die Priorität:
- Niedrigere Zahlen laufen zuerst.
- Standard-Priorität ist
100. - Bei gleicher Priorität entscheidet die Reihenfolge der Registrierung.
- Wirft ein Callback eine Ausnahme, wird der vorherige Wert wiederhergestellt (kein Datenverlust).
Warum eine Priorität jenseits von 100 für Module sinnvoll ist: Die Core-Filter arbeiten auf Standard-Priorität. Tragen Sie Ihr Modul mit einer höheren Zahl ein, läuft es nach dem Core und sieht bereits dessen Ergebnis — Sie verändern also den fertig aufgebauten Wert, statt ihm zuvorzukommen.
Fehlerschutz
Ein einzelner fehlerhafter Filter darf nicht die ganze Kette reißen. Wirft ein Callback eine Ausnahme, fängt die Registry sie ab, protokolliert eine Warnung, stellt den vorherigen Wert wieder her und arbeitet die übrigen Filter weiter ab:
// If this callback throws, the previous value survives
$filters->register('schneespur.dashboard.kpis', function (array $widgets): array {
throw new \Exception('Bug in my filter');
// → warning logged, previous value restored, next filter continues
}, 200);
Warum das wichtig ist: Ein Modul, das die Navigation oder das Dashboard verändert, soll im Fehlerfall nur seinen eigenen Beitrag verlieren — nicht die Oberfläche für alle anderen Module lahmlegen. Verlassen sollten Sie sich darauf trotzdem nicht: Ein Filter, der wirft, verliert seine Änderung still. Siehe dazu die Best Practices am Ende.
Verfügbare Filter-Hooks
Der Core stellt eine feste Menge benannter Hooks bereit. Jeder hat einen genauen Anwendungsort, einen Werttyp und manchmal einen zusätzlichen Kontext.
schneespur.navigation.items
Angewendet in: NavigationRegistry::getItems()
Wert: array<string, array> — gruppierte Navigationspunkte
Kontext: keiner
Erlaubt Modulen, Navigationspunkte hinzuzufügen, zu entfernen oder umzusortieren, nachdem der Core die gruppierte Liste gebaut hat.
$filters->register('schneespur.navigation.items', function (array $grouped): array {
// Add an item to the 'modules' group
$grouped['modules'][] = [
'group' => 'modules',
'slug' => 'my-custom-nav',
'label' => 'My Module',
'route' => 'admin.my-module.index',
'icon' => 'heroicon-o-star',
'order' => 100,
'permission' => null,
'route_check' => null,
'active_pattern' => 'admin.my-module.*',
'badge' => null,
];
return $grouped;
}, 150);
Für den Normalfall — einen Navigationspunkt anmelden — gibt es mit der
NavigationRegistry einen direkteren Weg (siehe Navigation und Dashboard).
Dieser Filter ist für die Fälle, in denen Sie die bereits fertige, gruppierte Liste als
Ganzes umordnen oder Einträge anderer Module beeinflussen wollen.
schneespur.dashboard.kpis
Angewendet in: Admin\DashboardController
Wert: array — Widget-Konfigurationen
Kontext: keiner
Erlaubt Modulen, Dashboard-Widgets hinzuzufügen oder zu verändern, nachdem die Core-Widgets
gebaut wurden. Anders als die DashboardWidgetRegistry arbeitet dieser Filter auf dem schon
fertig vorbereiteten Widget-Array.
$filters->register('schneespur.dashboard.kpis', function (array $widgets): array {
$widgets[] = [
'key' => 'my-custom-kpi',
'label' => 'Custom KPI',
'view' => 'my-module::widgets.kpi',
'order' => 250,
'size' => 'half',
];
return $widgets;
}, 150);
schneespur.job.notification.recipients
Angewendet in: SendJobCompletedNotification-Listener
Wert: array — Empfängerliste
Kontext: $job (das abgeschlossene Job-Model)
Erlaubt Modulen, Empfänger zu ergänzen oder zu filtern, wenn ein Auftrag abgeschlossen
wird. Dieser Hook bekommt mit $job einen zweiten Parameter — das abgeschlossene Job-Model
—, sodass Sie die Empfänger anhand der konkreten Daten des Auftrags bestimmen können.
$filters->register('schneespur.job.notification.recipients', function (array $recipients, $job): array {
// Add the dispatcher to recipients
if ($dispatcher = $job->workShift?->user) {
$recipients[] = ['email' => $dispatcher->email, 'name' => $dispatcher->name];
}
return $recipients;
}, 100);
schneespur.job.notification.channels
Angewendet in: NotificationChannelRegistry::dispatch()
Wert: array<string, NotificationChannelInterface> — aktive Kanäle
Kontext: $job (das benachrichtigte Job-Model)
Erlaubt Modulen zu steuern, über welche Benachrichtigungs-Kanäle ein bestimmter Auftrag versendet wird. So lassen sich Kanäle abhängig vom Auftragstyp gezielt abschalten:
$filters->register('schneespur.job.notification.channels', function (array $channels, $job): array {
// Disable Telegram for Kontrolle-type jobs
if ($job->type === JobType::Kontrolle) {
unset($channels['telegram']);
}
return $channels;
}, 100);
Eigene Hooks im Modul anlegen
Ein Filter-Hook ist nicht dem Core vorbehalten. Sie können in Ihrem Modul eigene Hooks definieren, auf die andere Module sich eintragen, um Ihre Daten zu erweitern:
// In your module's code
$filterRegistry = app(FilterRegistry::class);
$reportData = $filterRegistry->apply('my-module.report.data', $baseData, $customer);
Andere Module können sich dann auf my-module.report.data registrieren, um die Daten zu
verändern. Warum das nützlich ist: Damit machen Sie Ihr Modul selbst erweiterbar — die
gleiche Entkopplung, die der Core Ihnen bietet, geben Sie an nachgelagerte Module weiter,
ohne deren Code zu kennen.
Best Practices
- Geben Sie den veränderten Wert immer zurück — ein Filter muss den (gegebenenfalls geänderten) Wert zurückgeben.
- Nutzen Sie eine Priorität über
100für Modul-Filter, damit die Core-Filter zuerst laufen. - Benennen Sie eigene Hooks mit dem Modul-Slug als Präfix:
my-module.hook.name. - Behandeln Sie fehlende Daten umsichtig — der Wert kann bereits von einem anderen Filter verändert worden sein.
- Werfen Sie keine Ausnahmen — sie werden zwar abgefangen, aber Ihre Änderung geht dabei verloren.
Wie Module ihre Erweiterungspunkte grundsätzlich in boot() anmelden, zeigt das
ServiceProvider-Muster. Eng verwandt sind die
Events: Filter verändern einen Wert und geben ihn zurück, Events
melden ein Geschehen, ohne einen Wert zu erwarten.