# 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) | `core/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 core/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: `core/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: `core/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: `core/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 --config=tools/php-cs-fixer/.php-cs-fixer.dist.php --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\\*` |