Rename the top-level lib/ directory to core/ so the project root immediately communicates which code is core platform and which lives in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the Composer PSR-4 path mapping moves from lib/ to core/. Module-internal lib/ directories (modules/*/lib/) are untouched. Updated across the full stack: - composer.json PSR-4 mapping - bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php) - tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer) - 26 architecture contract tests - enforcement-policy, guard-catalog, quality-gates - all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/) - bin/qa-extended.sh search paths - .claude/settings.local.json permission paths Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner → Executor → Code Review (4 findings fixed) → Security Review → Acceptance Test → Finalizer) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.3 KiB
8.3 KiB
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 inApiBootstrap+ApiResponse), nicht pro Endpoint. - Scheduler-Run-/Job-Ereignisse werden zentral im
SchedulerRunServiceinSystemAuditServicegeschrieben. - 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 voncore/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
AuthorizationDecisionzurück; kein HTTP-Redirect, keine Fachlogik. - Werden über
AccessPolicyFactory+AuthorizationServiceaufgelö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
newnur im Composition Root und in*Factory.php. - In
pages/**,templates/**,core/Support/**und Gateway-/Service-Fallbacks nur überapp(Foo::class)auflösen. - In Actions bevorzugt direkte Service/Gateway-Bindings (
app(Service::class)) stattapp(...Factory::class)->create...(). - In
pages/admin/**keineFactory::class-Referenzen; nur direkteapp(Service::class)/app(Repository::class). - In
core/Service/**außerhalb von*Factory.phpkeine direktennew ...Factory(). - In
core/Service/**außerhalb von*Factory.phpkeine direktennew ...Service(),new ...Gateway()odernew ...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)
(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|Repositoryinpages/** - kein
DB::inpages/** - keine
app(...Factory::class)->create...-Ketten inpages/admin/** - keine Input-Superglobals in
pages/** - kein
ApiResponse::readJsonBody(...)inpages/api/v1/**
- keine direkten
4) Autorisierung und Scope
- Zentral über
AuthorizationServiceund Policy-Abilities. - Keine verteilten Einzelchecks als primäre Zugriffslogik.
- UI-Capabilities zentral über
UiAccessServiceauflösen. $viewAuth['layout']wird zentral im Front Controller aufgebaut (web/index.php), nicht in Templates.- Templates nutzen nur
$viewAuth['layout']bzw.$viewAuth['page'], keinecan('...')-Helper.
4.1) Erweiterung neuer Permissions (Starter-Kit)
- Permission-Key in
PermissionService+ Migration/Seed ergänzen. - Ability in passender Policy ergänzen (oder neue Operation-Ability).
- Bei UI-Bedarf Mapping zentral in
UiCapabilityMapergänzen. - Action setzt benötigte
$viewAuth['page']-Flags. - Template konsumiert nur
$viewAuth. - Architektur-/Service-Tests und Doku aktualisieren.
Details und Sonderfälle: /docs/howto-rbac-permissions-playbook.md
5) data().php-Standard
pages/**/data().phpsind ausschließlich GET-Endpunkte (gridRequireGetRequest()).- Query-Normalisierung zentral über
gridParseFilters($query, gridSchemaQuery($filterSchema)). - Pro Liste gibt es ein
filter-schema.phpim 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.
- Toolbar-Rendering zentral über
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()odergridEnumSanitizer(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']inpages/**. - Validierungsfehler intern über
FormErrorsaufbauen (formErrors(),formErrorsFrom()). - API-Validation-Antworten einheitlich über
ApiResponse::validationFromFormErrors(...). - Web-Templates bekommen weiterhin kompakte Listen (
$errors) ausFormErrors::toFlatList(). Flashnicht 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/**
- Schichtgrenzen eingehalten.
- AuthZ zentral.
- Instanziierungsregeln aus Abschnitt 3 eingehalten (inkl. Gate-Checks).
- Unit-Tests ergänzt, bei Strukturänderung zusätzlich Contract-Test.
phpunitundphpstangrün.