# Architektur-Kompass Letzte Aktualisierung: 2026-03-25 ## Ziel Verbindliche Kurzregeln für Architekturentscheidungen. ## Systembild - Server-gerenderte Admin-App + REST-API unter `/api/v1`. - Multi-Tenant-Isolation ist Standard. - Autorisierung läuft zentral über Policies. - Externe API-Verträge sind UUID-first. ## Schichtenübersicht ```mermaid flowchart TD Browser([Browser / API-Client]) subgraph APP["core/App/ — Composition Root"] DI["AppContainer + Registrars"] end subgraph SVC["core/Service/"] direction LR FAC["Factory"] SERVICE["Service"] GW["Gateway"] POL["Policy"] end subgraph REPO["core/Repository/"] REP["Repository"] end subgraph pages["pages/**"] ACT["Action (*.php)"] VIEW["View (*.phtml)"] end subgraph TPL["templates/"] LAY["Layouts & Partials"] end DB[(MariaDB)] DI -->|"ruft zur Laufzeit"| FAC FAC -->|"erzeugt"| SERVICE FAC -->|"erzeugt"| GW Browser --> ACT ACT -->|"Input · AuthZ · Response"| SERVICE ACT --> POL ACT --> VIEW VIEW --> LAY SERVICE --> GW GW --> REP SERVICE --> REP REP --> DB ``` ## Request-Flow 1. `web/index.php` bootstrappt Routing/Konfiguration und initialisiert den `AppContainer`. 2. Guard/Firewall/Session/Locale greifen. 3. Action in `pages/**` nimmt Input an. 4. Action delegiert an Service. 5. Service nutzt Repository für Datenzugriff. 6. Response wird als HTML/JSON/Datei ausgegeben. ## Schichtregeln (MUSS) - `pages/**`: Input, Auth/AuthZ, Orchestrierung, Response. - `core/Service/**`: Fachlogik und Anwendungsfluss. - `core/Repository/**`: SQL, Filter, Paging, Mapping. - `templates/**`: Rendering ohne Business-Logik. - Templates konsumieren nur vom Action-Layer vorbereitete View-Daten (`$viewAuth`, Form/ViewModel), keine direkten Permission-Helper. - Service-Auflösung in Actions/Helpers nur über `app(Foo::class)`. - In Actions bevorzugt direkte Klassen-Bindings (`app(Service::class)`) statt Factory-Ketten. - In `pages/admin/**` keine `Factory::class`-Referenzen. - Request-Input in Actions nur über `requestInput()` lesen. - Validierungsfehler über `FormErrors` führen und für APIs mit `ApiResponse::validationFromFormErrors(...)` ausgeben. - In `pages/api/v1/**` kein direktes JSON-Body-Lesen über `ApiResponse::readJsonBody(...)`. - `Flash` nur für Redirect-/Outcome-Meldungen verwenden, nicht als primäre Validation-Struktur. - Listen-Endpoints in `pages/**/data().php` sind GET-only (`gridRequireGetRequest()`). - Query-Filter in `data().php` über `gridParseFiltersFromSchemaFile(...)` + `filter-schema.php` normalisieren (enthält `limit`, `offset`, `order`, `dir` im Ergebnis). ## Instanziierungsregeln (MUSS) - Composition Root: `web/index.php` + `core/App/registerContainer.php` (→ Details: `/docs/explanation-di-container.md`). - `new ...Factory()` nur im Composition Root und in `*Factory.php`. - In `core/Service/**` außerhalb von `*Factory.php` keine direkten `new ...Factory()`. - In `core/Service/**` außerhalb von `*Factory.php` keine direkten `new ...Service()`, `new ...Gateway()` oder `new ...Repository()`. - Legacy-Helper (`accessServicesFactory()`, `userServicesFactory()` usw.) sind entfernt und werden nicht mehr genutzt. ## API- und Security-Regeln (MUSS) - Tenant-Scope und Permission nur serverseitig erzwingen. - Denials je Endpunkt als `403` oder `404` modellieren. - API-Fehlerformat konsistent halten (`error`, optional `errors`). - OpenAPI bei API-Änderungen im selben Merge aktualisieren. ## RBAC-UI-Standard (MUSS) - Ability-Entscheidungen laufen serverseitig über `AuthorizationService`. - Für Layout/Partials werden globale Capabilities zentral über `UiAccessService::layoutCapabilities()` + `UiCapabilityMap::LAYOUT` bereitgestellt. - Für einzelne Views setzen Actions explizit `$viewAuth['page']` (bevorzugt über `UiCapabilityMap` + `UiAccessService::pageCapabilities()`). - In `templates/**` keine direkten `can('...')`- oder Permission-Key-Checks. ## Frontend-Page-Module (MUSS) - In `pages/**` und `templates/**` sind inline `` ohne `src` verboten. - Seiteninitialisierung liegt in dedizierten ESM-Page-Modulen unter `web/js/pages/*`. - Pro Seite wird Konfiguration per `` bereitgestellt und im Modul über `readPageConfig(...)` gelesen. - In `pages/**` und `templates/**` müssen alle JS-`src` (eigene und Vendor) über `assetVersion('...js...')` laufen. ## Mindestchecks vor Merge ```bash docker compose exec php vendor/bin/phpunit docker compose exec php vendor/bin/phpstan analyse -c phpstan.neon --no-progress ``` Bei JS-Änderungen zusätzlich Browser-Smoke mit DevTools-Console (keine JS-Errors). Struktur-Gates zusätzlich: ```bash (rg 'new\s+[^\s(]+Factory\(' core/Service || true) | wc -l (rg --glob '!**/*Factory.php' 'new\s+[^\s(]+(Service|Gateway|Repository)\(' core/Service || true) | wc -l (rg "\b(accessServicesFactory|directoryServicesFactory|userServicesFactory|authServicesFactory|tenantServicesFactory|settingServicesFactory|userRepositoryFactory|authRepositoryFactory|authGatewayFactory)\(" pages core templates web || true) | wc -l (rg -n 'new\s+[^\s(]+(Service|Gateway|Repository)\(' pages -g '*.php' || true) | wc -l (rg -n '\bDB::' pages -g '*.php' || true) | wc -l (rg -n 'app\([^\n]*Factory::class\)->create' pages/admin -g '*.php' || true) | wc -l (rg -n 'Factory::class' pages/admin -g '*.php' || true) | wc -l (rg -n 'ApiResponse::readJsonBody\(' pages/api/v1 -g '*.php' || true) | wc -l ``` Architektur-Contract: ```bash docker compose exec php vendor/bin/phpunit tests/Architecture/CoreStarterkitContractTest.php ``` ## Vertiefung - `/docs/reference-core-standards.md` - `/docs/explanation-sicherheitsmodell.md` - `/docs/howto-rbac-permissions-playbook.md`