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.