Dokumentation durchblättern
Module entwickeln
Modul-Lebenszyklus
Wie Module gefunden, geladen, gebootet, aktiviert, deaktiviert, aktualisiert und entfernt werden — und worauf Sie bei Routen- und Config-Caching achten müssen.
Ein Modul durchläuft mehrere Stationen: von der Entdeckung auf der Festplatte über das Booten
bis zum geordneten Entfernen. Wer diese Stationen kennt, versteht, warum bestimmte Dinge nur
in boot() erlaubt sind und warum ein gecachter Routen-Stand Module unsichtbar machen kann.
Entdeckung (Discovery)
Der ModuleManager findet Module, indem er nach module.json-Dateien sucht:
modules/*/module.json
Jedes gültige Manifest wird intern mit seinem slug (dem Verzeichnisnamen) und seinem
path abgelegt.
// ModuleManager::discover()
$dirs = glob($this->modulesPath . '/*/module.json');
// Je Treffer: JSON parsen, slug aus dem Verzeichnisnamen ableiten, Manifest ablegen
Der Modul-Pfad ist standardmäßig base_path('modules') und lässt sich in
config/schneespur_modules.php ändern.
Boot-Sequenz
Für jedes entdeckte, aktivierte Modul führt ModuleManager::boot() aus:
- PSR-4-Autoloader registrieren — bildet den
namespacedes Moduls auf seinsrc/-Verzeichnis ab - Übersetzungs-Namespace ergänzen — macht das
lang/-Verzeichnis als{slug}::keyverfügbar - ServiceProvider instanziieren — die Klasse aus dem Manifest-Feld
service_provider register()aufrufen — Services in den Container bindenboot()aufrufen — in die Erweiterungs-Registries eintragen, Routen/Views/Events laden- Modul-Assets registrieren —
dist/manifest.jsonlesen, öffentlichen Symlink anlegen
Scheitert ein Schritt, wird das Modul automatisch deaktiviert und ein Diagnose-Ereignis gemeldet. Das ist Absicht: Ein kaputtes Modul soll die ganze Anwendung nicht mitreißen, sondern sich sauber selbst aus dem Weg räumen.
try {
$provider->register();
$provider->boot();
$this->registerModuleAssets($slug, $manifest['path']);
} catch (\Throwable $e) {
$this->autoDisable($slug, $e->getMessage());
$this->reportDiagnostic('module_boot_failed', $slug, $e);
}
Aktivieren / Deaktivieren
Ein Modul aktivieren
$manager = app(ModuleManager::class);
$result = $manager->enable('my-module');
if ($result === true) {
// Erfolg — das Modul ist jetzt aktiv
} else {
// $result ist ein Array von Fehler-Strings (Abhängigkeits-Fehler, not_found)
}
Vor dem Aktivieren prüft der DependencyValidator:
- alle
requires-Abhängigkeiten sind aktiv und versions-kompatibel - kein
conflicts-Eintrag ist aktiv - zirkuläre Abhängigkeiten werden per Tiefensuche (DFS) erkannt
Ein Modul deaktivieren
$result = $manager->disable('my-module');
if ($result === true) {
// Erfolg — das Modul ist jetzt deaktiviert
} else {
// $result ist ein Array von Modul-Slugs, die von diesem Modul abhängen
}
Vor dem Deaktivieren werden die umgekehrten Abhängigkeiten geprüft: Ein Modul, das ein anderes aktives Modul benötigt, lässt sich nicht deaktivieren — sonst stünde das abhängige Modul plötzlich ohne Fundament da.
Installation (aus dem Katalog)
Der Artisan-Command schneespur:modules-sync übernimmt die Remote-Installation:
- lädt den Modul-Katalog vom konfigurierten Server
- prüft die Katalog-Signaturen mit libsodium
- lädt neue/aktualisierte Modul-ZIPs herunter
- entpackt nach
modules/{slug}/über denSchneespurModuleInstaller - führt Migrationen aus:
php artisan migrate --path=modules/{slug}/database/migrations - erkennt verwaiste Module (lokal vorhanden, aber aus dem Katalog entfernt)
Entfernen
Der Command schneespur:modules-remove {slug}:
- deaktiviert das Modul
- rollt alle Modul-Migrationen zurück
- räumt die Modul-Einstellungen auf (
Setting::where('key', 'like', '{slug}.%')) - löscht die Modul-Dateien von der Platte
- entfernt den öffentlichen Symlink
Update-Ablauf
Updates erledigt SchneespurModuleInstaller::update():
- legt eine
.bak-Sicherung des aktuellen Modul-Verzeichnisses an - entpackt die neue ZIP-Version
- rollt bei einem Fehler aus der
.bakzurück - führt nach erfolgreichem Entpacken die Migrationen aus
Die .bak-Sicherung ist der Grund, warum ein fehlgeschlagenes Update nicht in einem
halb-entpackten Zustand endet: Entweder die neue Version steht vollständig, oder die alte
kommt zurück.
Zustands-Verwaltung
Welche Module installiert und welche aktiviert sind, wird an zwei Stellen geführt:
- Tabelle
modules— der dauerhafte Datensatz mit Slug, Version, Aktiv-Flag, Manifest und Signatur-Status storage/app/schneespur_modules_state.json— der Katalog-Sync-Status (ETag, Cache, Trust-Keys)
Abhängigkeits-Prüfung
Der DependencyValidator versteht Versions-Constraints in module.json:
| Constraint | Beispiel | Bedeutung |
|---|---|---|
* | "*" | jede Version |
>=X | ">=2.0.0" | mindestens Version X |
^X | "^1.2.0" | kompatibel (gleicher Major, >= Minor.Patch) |
~X | "~1.2.0" | gleicher Major.Minor, >= Patch |
Beispiel:
{
"requires": {
"notifications-core": "^1.0.0",
"another-module": ">=2.1.0"
},
"conflicts": ["legacy-module"]
}
Routen, Config und Caching
Das ist die Stolperstelle, an der die meisten Modul-Probleme im Betrieb entstehen.
Module registrieren ihre Routen (Route::group()) und mischen ihre Config (mergeConfigFrom)
dynamisch beim Boot ein — abhängig davon, welche Module in der Datenbank aktiviert sind.
Das verträgt sich schlecht mit den statischen Caches von Laravel:
php artisan route:cacheserialisiert nur die Routen, die im Moment des Cache-Baus registriert waren. Ein danach aktiviertes Modul ist nicht im Cache, und seine Laufzeit-Registrierung perRoute::group()wird von der gecachten Routen-Sammlung überdeckt →route('admin.<slug>.*')wirft eineRouteNotFoundException(500 auf der Admin-Navigation oder bei Dashboard-Widgets).php artisan config:cachefriert die Config genauso ein →config('<slug>.*')liefertnull, das Modul verhält sich zur Laufzeit falsch.
Es gibt zwei Wege, damit zu leben:
- Caches bei Modul-Änderungen leeren. Nach dem Aktivieren/Deaktivieren von Modulen
config:cacheundroute:cacheneu bauen (oder bei jedem Umschaltenoptimize:clearausführen). Nötig, wenn Sie aus Performance-Gründen cachen. - Routen/Config gar nicht cachen auf Deployments, die Module zur Laufzeit installieren. So macht es das offizielle Docker-/Coolify-Image: Es cached nur Views und Events.
Modul-Autoren sollten zusätzlich der Navigations-Registrierung den eigenen Routennamen als
routeCheck: mitgeben (siehe das Kapitel zur Navigation). Fehlt die Route, wird der
Menüpunkt dann ausgeblendet, statt einen 500-Fehler auszulösen — ein deaktiviertes oder noch
nicht gebootetes Modul fällt so leise aus der Oberfläche heraus.