diff --git a/docs/howto-modul-erstellen.md b/docs/howto-modul-erstellen.md new file mode 100644 index 0000000..7ac07ba --- /dev/null +++ b/docs/howto-modul-erstellen.md @@ -0,0 +1,363 @@ +# Neues Modul erstellen + +Letzte Aktualisierung: 2026-03-26 + +## Ziel + +Schritt-fuer-Schritt Anleitung: vom leeren Verzeichnis zum lauffaehigen Modul mit Route, Permission, UI-Slot und Test. + +## Voraussetzungen + +- Laufende Dev-Umgebung (`docker compose up`) +- Vertraut mit dem Schichtmodell (Service, Repository, Pages) — siehe `/docs/tutorial-04-architektur-request-flow.md` +- Vertraut mit Permissions — siehe `/docs/howto-rbac-permissions-playbook.md` + +## Referenzdokumentation + +| Was | Wo | +|---|---| +| Manifest-Contract (verbindlich) | `/docs/reference-module-manifest-contract.md` | +| Manifest-JSON-Schema | `/.agents/contracts/module-manifest.schema.json` | +| Platform-Contracts (Interfaces) | `lib/App/Module/Contracts/` (5 Interfaces) | +| CLI-Kommandos | `/docs/reference-cli-commands.md` | +| Referenzmodul (vollstaendig) | `modules/notifications/` | +| Einfaches Modul | `modules/bookmarks/` | + +## Schritt 1: Scaffold erzeugen + +```bash +docker compose exec php php bin/console make:module mein-modul +``` + +Erzeugt: + +``` +modules/mein-modul/ + module.php ← Manifest + lib/Module/MeinModul/ + MeinModulContainerRegistrar.php ← DI-Registrierung + MeinModulAuthorizationPolicy.php ← Berechtigungspruefung + pages/ ← Seiten (file-based routing) + templates/ ← Modul-spezifische Templates + web/css/ ← Stylesheets + web/js/ ← JavaScript + tests/Module/MeinModul/ ← PHPUnit-Tests +``` + +## Schritt 2: Modul aktivieren + +`config/modules.php` bearbeiten — Modul-ID in die Liste aufnehmen: + +```php +return [ + 'enabled_modules' => ['audit', 'addressbook', 'bookmarks', 'notifications', 'mein-modul'], +]; +``` + +Alternative: `APP_ENABLED_MODULES` Umgebungsvariable (ueberschreibt die Datei). + +## Schritt 3: Manifest konfigurieren + +Die Datei `modules/mein-modul/module.php` enthaelt alle Deklarationen. Hier ein Minimal-Beispiel mit einer Route und einer Permission: + +```php + 'mein-modul', + 'version' => '0.1.0', + 'enabled_by_default' => false, + 'load_order' => 100, + 'requires' => [], + + 'routes' => [ + ['path' => 'mein-modul', 'target' => 'mein-modul'], + ], + 'public_paths' => [], + 'container_registrars' => [ + \MintyPHP\Module\MeinModul\MeinModulContainerRegistrar::class, + ], + + 'ui_slots' => [], + + 'authorization_policies' => [ + \MintyPHP\Module\MeinModul\MeinModulAuthorizationPolicy::class, + ], + + 'search_resources' => [], + 'asset_groups' => [], + 'scheduler_jobs' => [], + 'layout_context_providers' => [], + 'session_providers' => [], + + 'permissions' => [ + [ + 'key' => 'mein_modul.view', + 'description' => 'Can view mein-modul', + 'active' => true, + 'is_system' => true, + ], + ], + + 'event_listeners' => [], + 'deactivation_handler' => null, + 'migrations_path' => null, + 'i18n_path' => null, +]; +``` + +Vollstaendige Felddokumentation: `/docs/reference-module-manifest-contract.md`. + +## Schritt 4: Authorization Policy im Container registrieren + +**Wichtig:** Die generierte `AuthorizationPolicy` hat einen Konstruktor-Parameter (`PermissionService`). Damit die Plattform sie instanziieren kann, muss sie im Container registriert werden. + +In `MeinModulContainerRegistrar.php`: + +```php +set(MeinModulAuthorizationPolicy::class, + static fn (AppContainer $c): MeinModulAuthorizationPolicy => new MeinModulAuthorizationPolicy( + $c->get(PermissionService::class) + ) + ); + + // Weitere Services hier registrieren. + } +} +``` + +Ohne diese Registrierung wird die Policy von `ModuleClassResolver` ignoriert und die Permission-Pruefung fuer UI-Slots schlaegt fehl (Slot wird nicht angezeigt). + +## Schritt 5: Erste Seite anlegen + +Datei `modules/mein-modul/pages/mein-modul/index().php`: + +```php + +

+``` + +## Schritt 6: Runtime synchronisieren + +```bash +docker compose exec php php bin/console module:sync +``` + +Dieser Befehl fuehrt automatisch aus: + +1. `module:migrate` — SQL-Migrationen anwenden +2. `module:permissions-sync` — Permissions registrieren +3. `module:build` — Seiten-Symlinks erstellen +4. `module:assets-sync` — CSS/JS-Assets veroeffentlichen + +Nach jedem Manifest-Aenderung erneut ausfuehren. + +## Schritt 7: Validieren + +```bash +docker compose exec php php bin/console module:validate mein-modul +``` + +Prueft: Manifest-Syntax, Klassen-Existenz, Namespace-Konventionen, Verzeichnisstruktur. + +## Schritt 8: Testen + +```bash +docker compose exec php vendor/bin/phpunit --filter=MeinModul +docker compose exec php vendor/bin/phpstan analyse -c phpstan.neon modules/mein-modul/ +``` + +Architektur-Tests (pruefen automatisch Namespace-Isolation, Session-Key-Prefix, Manifest-Contract): + +```bash +docker compose exec php vendor/bin/phpunit tests/Architecture/ +``` + +--- + +## Erweitern: Haeufige Erweiterungspunkte + +### UI-Slot: Sidebar-Tab + +```php +'ui_slots' => [ + 'aside.tab_panel' => [ + [ + 'key' => 'mein-modul', + 'label' => 'Mein Modul', + 'icon' => 'bi-puzzle', + 'href' => 'mein-modul', + 'permission' => 'mein_modul.view', + 'panel_template' => 'templates/aside-panel.phtml', + 'order' => 200, + ], + ], +], +``` + +Der `permission`-Wert muss dem Ability-String der AuthorizationPolicy entsprechen (hier: `mein_modul.view`). + +### UI-Slot: Topbar-Button + +```php +'ui_slots' => [ + 'topbar.right_item' => [ + [ + 'key' => 'mein-modul-button', + 'template' => 'templates/topbar-button.phtml', + 'permission' => 'mein_modul.view', + 'order' => 200, + ], + ], +], +``` + +### CSS/JS-Assets + +Dateien unter `modules/mein-modul/web/` werden per Symlink nach `web/modules/mein-modul/` veroeffentlicht. + +Fuer seitenspezifische Assets (`asset_groups`): + +```php +'asset_groups' => [ + 'mein-modul' => [ + 'modules/mein-modul/css/pages/mein-modul.css', + ], +], +``` + +Im View mit `style_groups` laden: + +```php +Buffer::set('style_groups', ['mein-modul']); +``` + +Fuer globale Assets (auf jeder Seite) stattdessen `layout.head_style` oder `runtime.component` verwenden. + +### Globale Suche + +`SearchResourceProvider` implementieren — siehe `/docs/howto-globale-suche.md` und `modules/addressbook/lib/Module/AddressBook/Providers/AddressBookSearchProvider.php`. + +### Session-Daten bei Login + +`SessionProvider` implementieren — populiert Modul-spezifische Session-Daten nach Login: + +```php +'session_providers' => [ + \MintyPHP\Module\MeinModul\Providers\MeinModulSessionProvider::class, +], +``` + +Interface: `lib/App/Module/Contracts/SessionProvider.php` (Methoden: `populate()`, `clear()`). + +**Session-Key-Konvention:** `module.mein-modul.*` (wird per Architektur-Test geprueft). + +### Layout-Kontext + +`LayoutContextProvider` implementieren — liefert Daten fuer Templates bei jedem Page-Render: + +```php +'layout_context_providers' => [ + \MintyPHP\Module\MeinModul\Providers\MeinModulLayoutProvider::class, +], +``` + +Interface: `lib/App/Module/Contracts/LayoutContextProvider.php` (Methode: `provide()`). + +**Key-Konvention:** Top-Level-Keys muessen namespaced sein: `mein-modul.nav`, nicht `nav`. + +### Event-Listener + +```php +'event_listeners' => [ + 'user.created' => [ + \MintyPHP\Module\MeinModul\Listeners\UserCreatedListener::class, + ], +], +``` + +Interface: `lib/App/Module/Contracts/EventListener.php` (Methode: `handle(string $event, array $payload)`). + +### Scheduler-Job + +Siehe `/docs/howto-geplante-aufgaben.md` und `modules/audit/module.php` fuer Beispiele. + +### Modul-eigene Datenbank + +```php +'migrations_path' => 'db/updates', +``` + +SQL-Dateien unter `modules/mein-modul/db/updates/` — idempotent (IF NOT EXISTS, ON DUPLICATE KEY). +Ausgefuehrt ueber `php bin/console module:migrate`. + +### Uebersetzungen + +```php +'i18n_path' => 'i18n', +``` + +Dateien: `modules/mein-modul/i18n/default_de.json`, `modules/mein-modul/i18n/default_en.json`. +Schluessel werden per `t('key')` aufgeloest — identisch zu Core-Translations. + +### Abhaengigkeit von anderem Modul + +```php +'requires' => ['notifications'], +``` + +Nicht-deklarierte Cross-Module-Imports sind verboten und werden per Architektur-Test geprueft. + +--- + +## Checkliste vor Merge + +- [ ] `module:validate` ist fehlerfrei +- [ ] `module:sync` laeuft ohne Fehler +- [ ] PHPUnit gruent (`vendor/bin/phpunit`) +- [ ] PHPStan Level 5 gruent (`vendor/bin/phpstan analyse -c phpstan.neon`) +- [ ] Architektur-Tests gruent (`tests/Architecture/`) +- [ ] PHP-CS-Fixer gruent (`vendor/bin/php-cs-fixer fix --dry-run`) +- [ ] AuthorizationPolicy ist im Container registriert (falls Konstruktor-Parameter vorhanden) +- [ ] Alle sichtbaren Texte nutzen `t('...')` +- [ ] Session-Keys verwenden `module..*` Prefix +- [ ] Keine Core-Namespaces (`MintyPHP\Service\*`, `MintyPHP\Repository\*`) im Modul-Code + +## Haeufige Fehler + +| Problem | Ursache | Loesung | +|---|---|---| +| Sidebar-Tab/UI-Slot wird nicht angezeigt | AuthorizationPolicy nicht im Container registriert | Policy in ContainerRegistrar mit `PermissionService` registrieren | +| 404 auf Modul-Route | `module:sync` nicht ausgefuehrt | `php bin/console module:sync` | +| Permission existiert nicht | Permission nicht in Manifest deklariert | `permissions`-Array in `module.php` ergaenzen, dann `module:sync` | +| CSS/JS werden nicht geladen | Assets nicht veroeffentlicht | `php bin/console module:assets-sync` oder `module:sync` | +| RuntimeException: fingerprint mismatch | Build veraltet nach Manifest-Aenderung | `php bin/console module:sync` | +| Namespace-Fehler in Tests | Modul-Klasse in Core-Namespace | Alle Klassen unter `MintyPHP\Module\\*` | diff --git a/docs/index.md b/docs/index.md index bcf77f6..0617a2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,6 +55,8 @@ Diese Dokumentation ist nach dem Diataxis-Modell in vier Quadranten gegliedert: - Haeufige Fehler und schnelle Loesungen. - `/docs/howto-lokale-entwicklung.md` - Tages-Workflow fuer Entwicklung und Checks. +- `/docs/howto-modul-erstellen.md` + - Neues Modul erstellen: Scaffold, Manifest, Route, Permission, UI-Slot, Test. - `/docs/howto-dokumentation-erweitern.md` - Regeln fuer konsistente, wachsende Doku. diff --git a/lib/Console/Commands/Make/ModuleCommand.php b/lib/Console/Commands/Make/ModuleCommand.php index 99b197f..dd01418 100644 --- a/lib/Console/Commands/Make/ModuleCommand.php +++ b/lib/Console/Commands/Make/ModuleCommand.php @@ -127,8 +127,13 @@ final class ModuleCommand extends Command fwrite(STDOUT, " modules/{$moduleId}/tests/Module/{$pascalName}/\n"); fwrite(STDOUT, "\nNext steps:\n"); fwrite(STDOUT, " 1. Add '{$moduleId}' to config/modules.php enabled_modules\n"); - fwrite(STDOUT, " 2. Edit module.php to define routes, permissions, and UI slots\n"); - fwrite(STDOUT, " 3. Run: php bin/console module:sync\n"); + fwrite(STDOUT, " 2. Register the AuthorizationPolicy in {$pascalName}ContainerRegistrar (required for UI slot visibility)\n"); + fwrite(STDOUT, " 3. Edit module.php to define routes, permissions, and UI slots\n"); + fwrite(STDOUT, " 4. Run: php bin/console module:sync\n"); + fwrite(STDOUT, "\nGuides:\n"); + fwrite(STDOUT, " Tutorial: docs/howto-modul-erstellen.md\n"); + fwrite(STDOUT, " Manifest contract: docs/reference-module-manifest-contract.md\n"); + fwrite(STDOUT, " Reference module: modules/notifications/ (full-featured example)\n"); return 0; }