forked from fa/breadcrumb-the-shire
155 lines
8.3 KiB
Markdown
155 lines
8.3 KiB
Markdown
# lib-Standards
|
|
|
|
Letzte Aktualisierung: 2026-03-09
|
|
|
|
Praktische Regeln nur für `lib/**`. Ergänzt `/docs/explanation-architektur.md`.
|
|
|
|
## 1) Repository-Regeln
|
|
|
|
- Verantwortlich für SQL, Filter, Paging und Mapping.
|
|
- Keine HTTP-, Session-, Redirect- oder Policy-Logik.
|
|
|
|
## 2) Service-Schicht im Detail
|
|
|
|
`lib/Service/` enthält vier klar getrennte Typen — jeder hat eine eigene Verantwortung:
|
|
|
|
### 2a) Service (`*Service.php`)
|
|
|
|
- Verantwortlich für Fachlogik und Orchestrierung.
|
|
- Koordiniert Repositories und Gateways; enthält Business-Regeln und Validierung.
|
|
- Keine Superglobals (`$_POST`, `$_GET`, `$_SESSION`).
|
|
- Keine Header- oder Redirect-Ausgabe.
|
|
- Keine direkten `DB::...`-Aufrufe.
|
|
- Abhängigkeiten per Constructor Injection.
|
|
- DB-Locks und Transaktionen über Repository-Schicht (z. B. `DatabaseSessionRepository`).
|
|
- Audit-Hooks für fachliche Schreibaktionen laufen in Services (`SystemAuditService`), nicht in Templates.
|
|
- API-Request-System-Audit läuft zentral über `ApiSystemAuditReporter` (Lifecycle in `ApiBootstrap` + `ApiResponse`), nicht pro Endpoint.
|
|
- Scheduler-Run-/Job-Ereignisse werden zentral im `SchedulerRunService` in `SystemAuditService` geschrieben.
|
|
- CLI-System-Audit für zentrale Commands wird am Entrypoint (`bin/**`) geschrieben, mit minimaler Metadata (ohne Argumentwerte).
|
|
|
|
### 2b) Gateway (`*Gateway.php`)
|
|
|
|
- Adapter-Schicht: kapselt eine einzelne externe Abhängigkeit für Services.
|
|
- Typische Abhängigkeiten: ein Repository, Settings-Zugriff, externe HTTP-APIs, Scope-Daten.
|
|
- Kein Fach-Orchestrierungsfluss — ein Gateway löst genau eine Infrastruktur-Frage.
|
|
- Beispiele: `AuthPermissionGateway` (Permission-Lookup), `UserSettingsGateway` (Settings-Zugriff), `OidcHttpGateway` (externe OIDC-API).
|
|
- Abhängigkeiten per Constructor Injection; keine direkten `new ...Repository()` außerhalb von Factories.
|
|
|
|
### 2c) Factory (`*Factory.php` / `*ServicesFactory.php` / `*GatewayFactory.php`)
|
|
|
|
- Einzige erlaubte Stelle für `new ...Service()`, `new ...Gateway()`, `new ...Repository()` innerhalb von `lib/Service/`.
|
|
- Erzeugt konfigurierte Service-/Gateway-Gruppen für den DI-Container.
|
|
- Keine Fachlogik; reine Kompositions-Helfer.
|
|
- Werden im Composition Root (`lib/App/registerContainer.php`) registriert.
|
|
|
|
### 2d) Policy (`*Policy.php` / `*AuthorizationPolicy.php`)
|
|
|
|
- Liegen in `lib/Service/Access/`.
|
|
- Treffen Authorization-Entscheidungen (Ability-Checks) für einen bestimmten Ressourcentyp.
|
|
- Geben `AuthorizationDecision` zurück; kein HTTP-Redirect, keine Fachlogik.
|
|
- Werden über `AccessPolicyFactory` + `AuthorizationService` aufgelöst — nie direkt in Actions instanziiert.
|
|
|
|
## 3) Instanziierung
|
|
|
|
- Composition Root ist `web/index.php` + `lib/App/registerContainer.php`
|
|
(→ Details: `/docs/explanation-di-container.md`).
|
|
- Domain-Abhängigkeiten mit `new` nur im Composition Root und in `*Factory.php`.
|
|
- In `pages/**`, `templates/**`, `lib/Support/**` und Gateway-/Service-Fallbacks nur über `app(Foo::class)` auflösen.
|
|
- In Actions bevorzugt direkte Service/Gateway-Bindings (`app(Service::class)`) statt `app(...Factory::class)->create...()`.
|
|
- In `pages/admin/**` keine `Factory::class`-Referenzen; nur direkte `app(Service::class)` / `app(Repository::class)`.
|
|
- 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-Service-Helper (`accessServicesFactory()`, `userServicesFactory()` usw.) werden nicht mehr verwendet.
|
|
- Für neue Domain-Logik keine statischen Utility-Klassen.
|
|
|
|
### 3.1) Schnell-Gates (lokal)
|
|
|
|
```bash
|
|
(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
|
|
```
|
|
|
|
Erwartung jeweils: `0`.
|
|
|
|
### 3.2) Architektur-Contract-Test
|
|
|
|
- Verbindlicher Guardrail: `tests/Architecture/CoreStarterkitContractTest.php`
|
|
- Deckt die Hard-Cut-Regeln ab:
|
|
- keine direkten `new ...Service|Gateway|Repository` in `pages/**`
|
|
- kein `DB::` in `pages/**`
|
|
- keine `app(...Factory::class)->create...`-Ketten in `pages/admin/**`
|
|
- keine Input-Superglobals in `pages/**`
|
|
- kein `ApiResponse::readJsonBody(...)` in `pages/api/v1/**`
|
|
|
|
## 4) Autorisierung und Scope
|
|
|
|
- Zentral über `AuthorizationService` und Policy-Abilities.
|
|
- Keine verteilten Einzelchecks als primäre Zugriffslogik.
|
|
- UI-Capabilities zentral über `UiAccessService` auflösen.
|
|
- `$viewAuth['layout']` wird zentral im Front Controller aufgebaut (`web/index.php`), nicht in Templates.
|
|
- Templates nutzen nur `$viewAuth['layout']` bzw. `$viewAuth['page']`, keine `can('...')`-Helper.
|
|
|
|
### 4.1) Erweiterung neuer Permissions (Starter-Kit)
|
|
|
|
1. Permission-Key in `PermissionService` + Migration/Seed ergänzen.
|
|
2. Ability in passender Policy ergänzen (oder neue Operation-Ability).
|
|
3. Bei UI-Bedarf Mapping zentral in `UiCapabilityMap` ergänzen.
|
|
4. Action setzt benötigte `$viewAuth['page']`-Flags.
|
|
5. Template konsumiert nur `$viewAuth`.
|
|
6. Architektur-/Service-Tests und Doku aktualisieren.
|
|
|
|
Details und Sonderfälle: `/docs/howto-rbac-permissions-playbook.md`
|
|
|
|
## 5) `data().php`-Standard
|
|
|
|
- `pages/**/data().php` sind ausschließlich GET-Endpunkte (`gridRequireGetRequest()`).
|
|
- Query-Normalisierung zentral über `gridParseFilters($query, gridSchemaQuery($filterSchema))`.
|
|
- Pro Liste gibt es ein `filter-schema.php` im selben Verzeichnis (Single Source of Truth).
|
|
- Schema-Typen: `int`, `number`, `bool`, `string`, `enum`, `uuid`, `date`, `csv_ids`, `csv_uuid`, `csv_strings`, `order`, `dir`.
|
|
- Keine Alias-Parameter (`*_id`) in Listen-Querys; IDs immer als CSV über `*_ids`.
|
|
- Bei JSON-Listenantworten immer konsistent: `{'data': [...], 'total': <int>}`.
|
|
- Für MultiSelect-Filter in Listen:
|
|
- Toolbar-Rendering zentral über `renderGridFilterToolbar(...)`.
|
|
- Select-/MultiSelect-/Text-/Date-/Hidden-Filter werden aus dem Schema gerendert (kein dupliziertes HTML in Templates).
|
|
- Grid-Filterbindung clientseitig zentral über `gridFiltersFromSchema(...)`.
|
|
- Grid-Filter verwenden CSV-Parameter (z. B. `event_types`, `actor_user_ids`), keine `[]`-Query-Arrays.
|
|
- Repository-Filter über `IN (???)` und serverseitige Limitierung/Validierung.
|
|
|
|
## 5.1) Status/Outcome-Taxonomien (Backed Enums)
|
|
|
|
- Persistierte Status-/Outcome-Domänen liegen zentral in `lib/Domain/Taxonomy/*`.
|
|
- Jede Taxonomie liefert einheitlich: `values()`, `tryNormalize()`, `normalizeOr()`, `labelToken()`, `badgeVariant()`.
|
|
- Filter-Schemas lesen Allowed-Listen aus `Enum::values()` oder `gridEnumSanitizer(Enum::class)`.
|
|
- Data-Endpunkte erzeugen Badge/Label über Enum-Cases, nicht über lokale `if/elseif`-Mappings.
|
|
- In Services/Repositories keine verstreuten Literal-Listen für diese Domänen (`success`, `failed`, ...).
|
|
- DB-seitig sind die gleichen Domänen per `CHECK`-Constraints abgesichert (`db/init/init.sql` + `db/updates/2026-03-03-status-taxonomy-hard-cut.sql`).
|
|
|
|
## 6) API-Contract
|
|
|
|
- Extern UUID-first.
|
|
- Interne IDs nur intern auflösen und mappen.
|
|
|
|
## 7) Input + Validation in `pages/**`
|
|
|
|
- Request-Daten nur über `requestInput()` lesen.
|
|
- Kein direktes `$_POST`, `$_GET`, `$_FILES`, `$_SERVER['REQUEST_METHOD']` in `pages/**`.
|
|
- Validierungsfehler intern über `FormErrors` aufbauen (`formErrors()`, `formErrorsFrom()`).
|
|
- API-Validation-Antworten einheitlich über `ApiResponse::validationFromFormErrors(...)`.
|
|
- Web-Templates bekommen weiterhin kompakte Listen (`$errors`) aus `FormErrors::toFlatList()`.
|
|
- `Flash` nicht als primäre Validation-Struktur verwenden; nur für Redirect-/Outcome-Meldungen.
|
|
|
|
Kurz-How-to: `/docs/howto-request-input-validation.md`
|
|
|
|
## 8) Definition of Done für `lib/**`
|
|
|
|
1. Schichtgrenzen eingehalten.
|
|
2. AuthZ zentral.
|
|
3. Instanziierungsregeln aus Abschnitt 3 eingehalten (inkl. Gate-Checks).
|
|
4. Unit-Tests ergänzt, bei Strukturänderung zusätzlich Contract-Test.
|
|
5. `phpunit` und `phpstan` grün.
|