Files
breadcrumb-the-shire/docs/howto-modul-erstellen.md
fs 545bcc789b docs: replace ASCII umlaut fallbacks with proper äöüß across all .md
Most German docs were written with `ue`/`ae`/`oe`/`ss` ASCII fallbacks
(`fuer`, `aenderung`, `koennen`, `gemaess`) — relic from older toolchain
constraints. Replaced 237 occurrences across 38 .md files with proper
umlauts and ß so the docs read naturally.

Substitutions are constrained to plain prose: fenced code blocks
(```...```), inline code spans (`...`), markdown link targets `[..](..)`,
and HTML comments are preserved verbatim. Path references like
`docs/howto-erste-aenderung.md` keep their original (umlaut-replaced)
filenames — only the surrounding prose is normalised.

Tool: bin/fix-md-umlauts.py (idempotent — no-op on already-clean files).
Re-runnable if ASCII fallbacks creep back in via copy-paste.

Doc-link-check + doc-drift-check stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:57:22 +02:00

10 KiB

Neues Modul erstellen

Letzte Aktualisierung: 2026-03-26

Ziel

Schritt-für-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

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:

return [
    'enabled_modules' => ['audit', 'addressbook', 'bookmarks', 'notifications', 'mein-modul'],
];

Alternative: APP_ENABLED_MODULES Umgebungsvariable (überschreibt 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

return [
    'id' => '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

namespace MintyPHP\Module\MeinModul;

use MintyPHP\App\AppContainer;
use MintyPHP\App\Container\ContainerRegistrar;
use MintyPHP\Service\Access\PermissionService;

final class MeinModulContainerRegistrar implements ContainerRegistrar
{
    public function register(AppContainer $container): void
    {
        $container->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-Prüfung für UI-Slots schlaegt fehl (Slot wird nicht angezeigt).

Schritt 5: Erste Seite anlegen

Datei modules/mein-modul/pages/mein-modul/index().php:

<?php

use MintyPHP\Buffer;
use MintyPHP\Support\Guard;

Guard::requireLogin();
Guard::requireAbilityOrForbidden(
    \MintyPHP\Module\MeinModul\MeinModulAuthorizationPolicy::ABILITY_VIEW
);

Buffer::set('title', t('Mein Modul'));

Datei modules/mein-modul/pages/mein-modul/index(default).phtml:

<h1><?php e(t('Mein Modul')); ?></h1>
<p><?php e(t('Hier kommt der Inhalt.')); ?></p>

Schritt 6: Runtime synchronisieren

docker compose exec php php bin/console module:sync

Dieser Befehl führt 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-Änderung erneut ausführen.

Schritt 7: Validieren

docker compose exec php php bin/console module:validate mein-modul

Prüft: Manifest-Syntax, Klassen-Existenz, Namespace-Konventionen, Verzeichnisstruktur.

Schritt 8: Testen

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 (prüfen automatisch Namespace-Isolation, Session-Key-Prefix, Manifest-Contract):

docker compose exec php vendor/bin/phpunit tests/Architecture/

Erweitern: Häufige Erweiterungspunkte

UI-Slot: Sidebar-Tab

'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

'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):

'asset_groups' => [
    'mein-modul' => [
        'modules/mein-modul/css/pages/mein-modul.css',
    ],
],

Im View mit style_groups laden:

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:

'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 geprüft).

Layout-Kontext

LayoutContextProvider implementieren — liefert Daten für Templates bei jedem Page-Render:

'layout_context_providers' => [
    \MintyPHP\Module\MeinModul\Providers\MeinModulLayoutProvider::class,
],

Interface: core/App/Module/Contracts/LayoutContextProvider.php (Methode: provide()).

Key-Konvention: Top-Level-Keys müssen namespaced sein: mein-modul.nav, nicht nav.

Event-Listener

'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 für Beispiele.

Modul-eigene Datenbank

'migrations_path' => 'db/updates',

SQL-Dateien unter modules/mein-modul/db/updates/ — idempotent (IF NOT EXISTS, ON DUPLICATE KEY). Ausgeführt über php bin/console module:migrate.

Übersetzungen

'i18n_path' => 'i18n',

Dateien: modules/mein-modul/i18n/default_de.json, modules/mein-modul/i18n/default_en.json. Schlüssel werden per t('key') aufgeloest — identisch zu Core-Translations.

Abhaengigkeit von anderem Modul

'requires' => ['notifications'],

Nicht-deklarierte Cross-Module-Imports sind verboten und werden per Architektur-Test geprüft.


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.<id>.* Prefix
  • Keine Core-Namespaces (MintyPHP\Service\*, MintyPHP\Repository\*) im Modul-Code

Häufige Fehler

Problem Ursache Lösung
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 ausgeführt 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-Änderung php bin/console module:sync
Namespace-Fehler in Tests Modul-Klasse in Core-Namespace Alle Klassen unter MintyPHP\Module\<Name>\*