Files
breadcrumb-the-shire/docs/explanation-architektur.md
fs 3614421eda fix(docs): resolve documentation drift and add missing content
- Fix fictional SettingGateway/SettingService references with actual
  multi-gateway pattern (AdminSettingsService + domain-specific gateways)
- Replace non-existent gridQueryLimitOffset()/gridQueryOrderDir() with
  actual gridParseFiltersFromSchemaFile()
- Normalize bin/doctor.php references to canonical bin/console doctor
- Add i18n_path contract to module manifest docs
- Document module:validate and make:module CLI commands
- Add APP_VENDOR_PHP_ISSUES_MODE env var to config reference
- Remove invalid label field from notifications module permissions
  (violates manifest schema additionalProperties: false)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:15:53 +01:00

5.8 KiB

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

flowchart TD
    Browser([Browser / API-Client])

    subgraph APP["lib/App/ — Composition Root"]
        DI["AppContainer + Registrars"]
    end

    subgraph SVC["lib/Service/"]
        direction LR
        FAC["Factory"]
        SERVICE["Service"]
        GW["Gateway"]
        POL["Policy"]
    end

    subgraph REPO["lib/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.
  • lib/Service/**: Fachlogik und Anwendungsfluss.
  • lib/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 + lib/App/registerContainer.php (→ Details: /docs/explanation-di-container.md).
  • new ...Factory() nur im Composition Root und in *Factory.php.
  • In lib/Service/** außerhalb von *Factory.php keine direkten new ...Factory().
  • In lib/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 <script type="module">...</script> ohne src verboten.
  • Seiteninitialisierung liegt in dedizierten ESM-Page-Modulen unter web/js/pages/*.
  • Pro Seite wird Konfiguration per <script type="application/json" id="page-config-<page-key>">...</script> bereitgestellt und im Modul über readPageConfig(...) gelesen.
  • In pages/** und templates/** muessen alle JS-src (eigene und Vendor) ueber assetVersion('...js...') laufen.

Mindestchecks vor Merge

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:

(rg 'new\s+[^\s(]+Factory\(' lib/Service || true) | wc -l
(rg --glob '!**/*Factory.php' 'new\s+[^\s(]+(Service|Gateway|Repository)\(' lib/Service || true) | wc -l
(rg "\b(accessServicesFactory|directoryServicesFactory|userServicesFactory|authServicesFactory|tenantServicesFactory|settingServicesFactory|userRepositoryFactory|authRepositoryFactory|authGatewayFactory)\(" pages lib 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:

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

Vertiefung

  • /docs/reference-lib-standards.md
  • /docs/explanation-sicherheitsmodell.md
  • /docs/howto-rbac-permissions-playbook.md