- remove legacy bin/module-*.php and bin/doctor.php entry scripts - move module/doctor execution into class-based runners under lib/Console - add stable JSON output for doctor and module:sync - introduce bin/dev for init/up/down/logs/console/qa workflow - update DI wiring, phpstan scan config, tests, and docs to new CLI contract
5.2 KiB
Module Manifest Contract (V1.2)
Letzte Aktualisierung: 2026-03-25
Ziel
Verbindlicher Contract fuer modules/*/module.php, damit Module zur Runtime deterministisch geladen und validiert werden.
Status
Dieses Dokument ist die kanonische (normative) Quelle fuer Manifest-, Runtime- und Guard-Regeln des Modul-Systems.
/docs/reference-extension-readiness.md referenziert diesen Contract und dupliziert keine Felddefinitionen.
Namespace-Konvention
Alle PHP-Klassen eines Moduls muessen den Namespace MintyPHP\Module\<Name>\* verwenden.
Core-Namespaces (MintyPHP\Service\*, MintyPHP\Repository\*, MintyPHP\Support\*) sind fuer Modul-Code verboten.
Verzeichnis-Layout:
modules/<id>/lib/Module/<Name>/
Service/ # BookmarkService, Factory, etc.
Repository/ # Repository + Interfaces
Providers/ # SessionProvider, LayoutProvider, SearchProvider
Support/ # Modul-spezifische Utilities
<Name>ContainerRegistrar.php
<Name>AuthorizationPolicy.php
Der ModuleAutoloader mappt MintyPHP\* → modules/<id>/lib/ per PSR-4.
Enforcement: ModuleStructureContractTest::testModuleClassesUseModuleNamespace.
Aktivierung (APP_ENABLED_MODULES)
- nicht gesetzt:
config/modules.phpgilt. - gesetzt und leer (
APP_ENABLED_MODULES=''): keine Module. - gesetzt mit Liste (
APP_ENABLED_MODULES='a,b'): exakt diese Module.
i18n_path Contract
Module mit eigenen Übersetzungsdateien deklarieren den relativen Pfad zum i18n-Verzeichnis:
'i18n_path' => 'i18n',
Der ModuleAutoloader registriert daraus den absoluten Pfad modules/<id>/i18n/ als zusätzliche Übersetzungsquelle.
permissions Contract
permissions ist eine Objektliste:
'permissions' => [
[
'key' => 'module.permission',
'description' => 'Human readable text',
'active' => true, // optional, default true
'is_system' => true, // optional, default true
],
],
keyunddescriptionsind Pflichtfelder.- Runtime-Sync erfolgt ueber
php bin/console module:sync. module:syncfuehrt diesen Sync automatisch aus.
scheduler_jobs Contract
scheduler_jobs ist eine Objektliste mit vollem Default-Contract:
'scheduler_jobs' => [
[
'job_key' => 'module.job',
'handler' => App\\Scheduler\\MyJobHandler::class,
'label' => 'Job label',
'description' => 'Job description',
'default_enabled' => true,
'default_timezone' => 'Europe/Berlin',
'default_schedule_type' => 'daily', // hourly|daily|weekly
'default_schedule_interval' => 1,
'default_schedule_time' => '02:00', // required for daily/weekly
'default_schedule_weekdays_csv' => null, // weekly: e.g. '1,3,5'
'default_catchup_once' => true,
'allowed_schedule_types' => ['daily', 'weekly'],
],
],
- Manifest ist Source of Truth fuer Job-Metadaten und Defaults.
- Handler werden zur Laufzeit container-resolvable fuer
execute(...)geladen.
ui_slots Contract (V1.2)
Neue generische Slot-Typen:
topbar.right_item:{ key, template, permission?, order }layout.body_end_template:{ key, template, permission?, order }layout.head_style:{ key, path, permission?, order }runtime.component:{ key, script, export?, selector?, scope?, config_path?, default_config?, phase?, permission?, order }
runtime.component.phase ist early|default|late (Default: late).
Abgrenzung CSS:
layout.head_style= global auf allen Seiten.asset_groups= seiten-/kontextbezogen ueberstyle_groups.
Guard-Regeln
- Route-Kollisionen sind fail-fast:
- Modul vs Modul auf
targetundpath. - Modul vs Core auf
pathbeim Router-Boot.
- Modul vs Modul auf
- Layout-Provider duerfen reservierte Core-Keys nicht ueberschreiben (
moduleSlots,csrfKey,currentTenant, ...). - Layout-Provider-Top-Level-Keys muessen namespaced sein (
<module_id>.<key>).
Runtime-Fingerprint
web/index.php validiert bei jedem Request, dass der Runtime-Stand zur aktuellen Modul-Konfiguration passt.
php bin/console module:buildschreibt nach jedem Buildstorage/runtime/.module-fingerprint(sortierte Modul-IDs, kommasepariert).web/index.phpvergleicht den Fingerprint mit der erwarteten Modul-Liste.- Bei Mismatch:
RuntimeExceptionmit Hinweis aufphp bin/console module:sync. - Kein Auto-Build im Request-Pfad — Build ist ausschliesslich CLI-Aufgabe.
Methoden: ModuleRuntimePageBuilder::expectedFingerprint(), readFingerprint(), writeFingerprint().
API-Channel-Isolation
API-Requests (api/-Prefix) ueberspringen den gesamten Web-Session-Flow:
- Kein
Session::start(), kein Cookie-Setting, kein CSRF-Token - Kein Remember-Me, kein Session-Timeout, kein Tenant-UI-Refresh
- API-Auth erfolgt ueber
ApiBootstrap.php(Bearer-Token) AccessControllistetapi/als always-public
Standard-Runbook
php bin/console module:sync fuehrt in fester Reihenfolge aus:
module:migratemodule:permissions-syncmodule:buildmodule:assets-sync
Der Output enthaelt eine deterministische Step-Summary (migrate, permissions-sync, build, assets-sync) inklusive vendor_warnings_ignored=<n>.
First-party Warnings/Notices/Deprecations gelten als Fehler (fail-fast).