# lib-Standards

Letzte Aktualisierung: 2026-03-09

Praktische Regeln nur für `core/**`. 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

`core/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 `core/Service/`.
- Erzeugt konfigurierte Service-/Gateway-Gruppen für den DI-Container.
- Keine Fachlogik; reine Kompositions-Helfer.
- Werden im Composition Root (`core/App/registerContainer.php`) registriert.

### 2d) Policy (`*Policy.php` / `*AuthorizationPolicy.php`)

- Liegen in `core/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` + `core/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/**`, `core/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 `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-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\(' 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 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 `core/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 `core/**`

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.
