# Architektur Letzte Aktualisierung: 2026-02-22 (geprüft) ## 1) Zielbild Die Anwendung ist eine servergerenderte Multi-Tenant-Admin-Anwendung mit klarer Trennung von: - HTTP/Request-Flow (`/web/index.php`, `lib/Http/*`) - Action-Layer (`/pages/**`) - Business-Logik (`/lib/Service/**`) - Datenzugriff (`/lib/Repository/**`) - Rendering (`/templates/**`, `*.phtml`) - Frontend-Verhalten (`/web/js/**`) ## 2) Request-Lebenszyklus Zentraler Entry Point ist `/web/index.php`. Ablauf pro Request: 1. Bootstrap (`vendor/autoload.php`, `config/config.php`, `config/router.php`, Helper) 2. Firewall + Session Start 3. Locale-Ermittlung (URL/Session/Cookie/default) 4. Remember-Me Auto-Login 5. Optionales Tenant-Snapshot-Refresh in Session 6. Redirect auf locale-prefixed URL (falls nötig) 7. Access Guard (`AccessControl`) für Public/Protected Paths 8. Action-Layer aus `pages/**` laden 9. View + Template rendern 10. Session beenden, DB schließen ## 3) Routing-Modell - Route-Definitionen (Pfad-Aliase) liegen in `/config/routes.php`. - Registrierung erfolgt in `/config/router.php` (liest nur `path` und `target`; das `public`-Feld wird vom Router nicht ausgewertet). - Welche Pfade ohne Login erreichbar sind, steuert `lib/Http/AccessControl.php`: - Hartkodierte `ALWAYS_PUBLIC_PREFIXES` (z. B. `auth/microsoft/`, `api/`, `branding/`, `flash/`) - `APP_PUBLIC_PATHS`-Konstante (gesetzt in `config/config.php`) MintyPHP-Konvention (Dateinamensrouting): - `pages/admin/users/edit($id).php` erwartet URL-Parameter `id` - `pages/admin/users/data().php` für Datenendpunkte - `...(none).phtml` für responses ohne Template-Layout - Binärausgaben (z. B. Onboarding-PDF/ZIP) setzen `MINTY_ALLOW_OUTPUT` im Action-Endpoint. ## 4) Schichten und Verantwortungen ### Action-Layer (`/pages`) - Eingaben lesen/validieren - Permissions und Tenant-Scope prüfen - Services aufrufen - View-Daten vorbereiten ### Service-Layer (`/lib/Service`) - Geschäftsregeln - Cross-Cutting-Logik (z. B. Auth, Settings, Branding) - Orchestrierung mehrerer Repositories - Scheduler/Lifecycle sind instanzbasiert über `lib/Service/Scheduler/SchedulerServicesFactory.php` verdrahtet. - Import/Import-Audit sind instanzbasiert über `lib/Service/Import/ImportServicesFactory.php` verdrahtet. - User-Domain ist instanzbasiert über `lib/Service/User/UserServicesFactory.php` verdrahtet (Services: `UserAccountService`, `UserAssignmentService`, `UserTenantContextService`, `UserPasswordService`; Repositories: `UserReadRepository`, `UserWriteRepository`, `UserListQueryRepository` + Assignment-Repositories). - Auth-/SSO-Domain ist instanzbasiert über `lib/Service/Auth/AuthServicesFactory.php` verdrahtet (u. a. `AuthService`, `RememberMeService`, `SsoUserLinkService`, `TenantSsoService`, `MicrosoftOidcService`). - Auth-Infra-Repositories (`ApiTokenRepository`, `RememberTokenRepository`, `PasswordResetRepository`, `EmailVerificationRepository`) werden ebenfalls instanzbasiert über die Factory injiziert. ### Repository-Layer (`/lib/Repository`) - SQL und Mapping - Filter-/Paging-Mechaniken - Keine UI-/HTTP-Logik ### Template/View-Layer (`/templates`, `*.phtml`) - Nur Rendering - Keine DB-Zugriffe - Wiederkehrende UI-Teile als Partials ## 5) Datenmodell (fachlich) Kernobjekte: - `users` - `tenants` - `departments` - `roles` - `permissions` Verknüpfungen: - `user_tenants`, `user_departments`, `user_roles` - `role_permissions` - `tenant_auth_microsoft`, `user_external_identities` - `tenant_custom_field_definitions`, `tenant_custom_field_options` - `user_custom_field_values`, `user_custom_field_value_options` Hinweis: - `departments` ist 1:1 an `tenants` gebunden über `departments.tenant_id` (kein M:N-Mapping). - In der User-Organisation wird die Department-Auswahl tenantweise gerendert (ein Multi-Select je zugewiesenem Tenant), gespeichert wird weiterhin flach über `user_departments`. - Tenant-spezifische User-Zusatzfelder werden separat modelliert: - Definitionen/Optionen pro Tenant in `tenant_custom_field_*` - Werte pro User in `user_custom_field_*` - Filterung im Address Book über dynamische Query-Keys `cf_*`, `cfm_*`, `cfd_*`. - `field_key` bleibt intern (tenant-weit eindeutig), wird im Tenant-UI automatisch aus dem Label erzeugt und nicht manuell gepflegt. - `is_filterable` ist fachlich nur für `select`, `multiselect`, `boolean`, `date` aktiv. - User-Zusatzfeldwerte sind in V1 optional (keine Pflichtvalidierung). - Tenant-Theme-Overrides werden direkt auf `tenants` gespeichert: - `tenants.default_theme` (`NULL` = globales `app_theme`) - `tenants.allow_user_theme` (`NULL` = globales `app_theme_user`). - Auflösung immer über `$_SESSION['current_tenant']` und damit tenant-spezifisch für Multi-Tenant-User. - Tenant-SSO für Microsoft Entra ID: - Konfiguration pro Tenant in `tenant_auth_microsoft`. - Externe Identitäts-Verknüpfung stabil über `user_external_identities` (`provider + tid + oid`). - Login-Endpunkte: `login` (optional `?tenant={slug}`), `auth/microsoft/start`, `auth/microsoft/callback`. - Shared-App-Credentials liegen global in `settings`; optionale Tenant-Overrides sind möglich. - Profil-Sync wird tenant-spezifisch über `tenant_auth_microsoft.sync_profile_on_login` und `tenant_auth_microsoft.sync_profile_fields` gesteuert. - Erlaubte Sync-Felder in V1: `first_name`, `last_name`, `phone`, `mobile`, `avatar`. - `phone/mobile/avatar` kommen über Microsoft Graph (`/me`, `/me/photo/$value`) und sind fail-open (Graph-Fehler blockieren den Login nicht). Sicherheits-/Audit-nahe Tabellen: - `user_remember_tokens` - `password_resets` - `email_verifications` - `mail_log` ## 6) Sicherheits- und Scope-Modell - Auth Guard: protected by default - Permission Checks: feature-spezifisch in Actions/Services - Tenant Scope: datenabhängig über Tenant-Matches - Strictness steuerbar über `TENANT_SCOPE_STRICT` Details siehe: `/docs/sicherheitsmodell.md` ## 7) Frontend-Architektur JS-Struktur: - `/web/js/core` Basismodule (DOM/Grid Factory) - `/web/js/components` UI-Komponenten - `/web/js/pages` seitenspezifische Helfer Bootstrap: - `app-boot.js` früh (no-js/js state, localStorage UI state) - `app-init.js` für Default-Template - `app-login-init.js` für Login-Template CSS-Struktur: - Layer-Entrypoint: `/web/css/app-layers.css` - Basen/Variablen in `/web/css/base` - Layout in `/web/css/layout` - Komponenten in `/web/css/components` - Seitenstile in `/web/css/pages` - Vendor-Overrides in `/web/css/vendor-overrides` ## 8) Asset-Steuerung Konfiguration liegt in `/config/assets.php`. - Template-Gruppen: `default`, `login` - Seiten-Gruppen optional via `Buffer::set('style_groups', ...)` - Rendering über Helper: `renderTemplateStyles()` und `renderPageStyles()` ## 9) Erweiterungsstrategie Pragmatischer Weg für neue Features: 1. Repository erweitern (SQL) 2. Service-Regel bauen 3. Action anbinden 4. View/Partial integrieren 5. JS nur falls interaktiv nötig 6. Tests + Lint/Analyse laufen lassen So bleibt die Trennung stabil und Refactoring kontrollierbar. ## 10) Settings-Architektur (DB + Datei-Cache) - `settings` (DB) ist die alleinige fachliche Quelle für globale Settings. - `config/settings.php` ist ein technischer Teil-Cache für sehr häufig gelesene UI-Basiswerte. - Zugriffsmuster: - `SettingGateway` (verdrahtet über `SettingServicesFactory`) -> `SettingService` -> DB - `appSetting(...)` -> `SettingServicesFactory` -> `SettingCacheService` (Datei-Cache) - Konsequenz: - Es ist korrekt, dass nicht alle DB-Settings im Datei-Cache stehen (z. B. SMTP/SSO/API-Details). - Der Cache muss nur die Keys enthalten, die im Runtime-Helper `appSetting(...)` verwendet werden. Details: `/docs/einstellungen-speicherung.md`