95723cebd96ee237b52ab2fde73a05f05dc5ee69
Codifies the convention that shared form partials (pages/admin/*/_form.phtml, modules/**/_form.phtml) either guard variable reads with ?? / isset() / array access checks, or have every consuming view template define the variable before requiring the partial. The convention was already followed by 6 of 7 partials in the codebase. The seventh (tenants/_form.phtml) regressed silently when a per-theme logo block introduced bare $canUpdateTenant reads in commit6e3fc63c— fixed in commite29e6c3. This test catches that exact bug shape and any future variant. Implementation uses token_get_all (no full PHP parser, no expression evaluation). For each partial it identifies variable references that are not locally defined or guarded, then walks the consuming templates discovered via require statements and verifies each variable is present before the require. On detection of statically unrecognizable constructs (extract(), dynamic require paths) the test fails loudly with a "review manually" hint rather than silently passing. Verified by reverting commite29e6c3in the working tree and running the test — it produces the exact $canUpdateTenant / line 223 finding that prompted the original fix. Restored after the dry-run. ALLOWLIST stays empty today. Future legitimate exceptions go in the test header with per-entry justification, mirroring DetailDrawerFragmentContractTest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CoreCore (MintyPHP)
CoreCore ist eine mandantenfaehige Admin-Anwendung auf Basis von MintyPHP. Der Fokus liegt auf klarer Rollen-/Rechtestruktur, Tenant-Scope und einer konsistenten Admin-UI.
Funktionsumfang
- Mandantenverwaltung (inkl. Branding/Farbe/Favicon)
- Abteilungen pro Mandant
- Benutzerverwaltung mit Rollen, Sichtbarkeit und Stammdaten
- Rollen- und Berechtigungsverwaltung
- Adressbuch (tenant-scoped, read-only Profilansicht)
- Globale Suche (Admin + Address Book)
- Mail-Versand (SMTP/PHPMailer) inkl. Mail-Log
- Onboarding-PDF fuer Zugangsdaten (Single inline, Bulk als ZIP)
- Passwort-Reset und Remember-Login-Tokens
- Geplante Aufgaben (Scheduler mit Ausfuehrungshistorie)
Tech Stack
- PHP 8.5 (Runtime im Docker-Container)
- MintyPHP Core
- MariaDB 11
- Memcached
- Nginx
- Grid.js (Tabellen)
- PHPMailer
- Dompdf (PDF Rendering)
- endroid/qr-code (QR-Code Rendering)
Webserver-Policy
- Docker-Betrieb ist Nginx-only (Dev + Prod).
- Apache-/
.htaccess-Regeln werden nicht ausgewertet und sind kein Teil des Laufzeit-Contracts. - Routing und Front-Controller-Verhalten werden ausschliesslich in
/docker/nginx/default.confund/docker/nginx/prod.confgepflegt.
Schnellstart (Docker)
- Umgebungsdatei anlegen
cp .env.example .env
- Config-Template fuer den App-Bootstrap anlegen
cp config/config.php.example config/config.php
- Container starten
docker compose up --build -d
- App oeffnen
- App: http://localhost:8080
- phpMyAdmin: http://localhost:8081
- Container stoppen
docker compose down
Alternativ ueber den Dev-Orchestrator:
cp config/config.php.example config/config.php
bin/dev init
bin/dev up
bin/dev down
bin/dev console list
bin/dev qa
Produktion (Docker)
Es gibt eine getrennte Produktions-Compose:
- Dev:
/docker-compose.yml - Prod:
/docker-compose.prod.yml
Grundschritte:
.envauf Produktionswerte setzen (siehe/.env.prod.example)- Domain und TLS in
/docker/nginx/prod.confanpassen - Zertifikate unter
/docker/nginx/certs/bereitstellen - Starten:
docker compose -f docker-compose.prod.yml up --build -d
Details stehen in:
/docs/howto-docker-lokal.md/docs/howto-docker-produktiv.md/docs/explanation-docker-betrieb.md(Übersicht)
Seed-Daten
Die Initialisierung liegt in /db/init/init.sql.
Standard-Demo-User:
- E-Mail:
demo@user.com - Passwort:
Demo123 - Tenant:
MusterMandant - Abteilung:
Musterabteilung - Rollen:
Admin,User
Initiale DB-Settings (Seed):
app_title->CoreCoreapp_locale->deapp_theme->lightapp_theme_user->1app_registration->1(aktiv)app_primary_color->#105433api_token_default_ttl_days->90api_token_max_ttl_days->365api_cors_allowed_origins->http://localhost:8080,http://127.0.0.1:8080session_idle_timeout_minutes->30session_absolute_timeout_hours->8user_inactivity_deactivate_days->180user_inactivity_delete_days->365default_tenant_id->MusterMandantdefault_department_id-> AbteilungscodeMUSTER(Musterabteilung)default_role_id-> RolleUSER(User)frontend_telemetry_enabled->1frontend_telemetry_sample_rate->0.2frontend_telemetry_allowed_events->ajax_error,warn_once
Projektstruktur
/configKonfiguration (Routen, Assets, Themes, Module + ENV-Bootstrap)/db/initDatenbankschema + Seeds/libBackend-Code (Repository,Service,Http,Support)/pagesAction-Layer + Views pro Route/templatesLayouts + wiederverwendbare Partials/weboeffentliche Assets (CSS/JS/Vendor)/i18nUebersetzungen/testsPHPUnit-Tests/docsProjekt-Dokumentation
Architektur-Kurzregeln
- Keine DB-Zugriffe in
.phtml-Views. - SQL bleibt in Repositories.
- Geschaeftslogik bleibt in Services.
pages/.../*.phpfungiert als Action-/Controller-Schicht.- Service-Aufloesung in Actions/Helpern ueber
app(Foo::class). new ...Factory()nur im Composition Root (web/index.php+core/App/registerContainer.php) oder in*Factory.php.pages/**/data().phpsind GET-only (gridRequireGetRequest()) und normalisierenlimit/offset/order/dirueber Grid-Helper.- Permissions + Tenant-Scope werden in Actions/Services erzwungen.
- Wiederkehrende UI-Bloecke als Partials zentralisieren.
UI-Konventionen
Edit-Seiten verwenden einheitliche Tab-Struktur:
- erster Tab:
Master data - letzter Tab:
Visibility - optional letzter Tab:
Danger zone
User-Organisation:
- Departments werden tenantweise angezeigt (ein Multi-Select je ausgewaehltem Tenant).
- Nach Tenant-Aenderungen im selben Formular werden Department-Optionen nach Speichern/Reload aktualisiert (kein Live-Update).
Zentrale Partials:
/templates/partials/app-details-titlebar.phtml/templates/partials/app-visibility-status-field.phtml/templates/partials/app-danger-zone-delete-field.phtml/templates/partials/app-details-aside-audit.phtml/templates/partials/app-details-aside-ids.phtml
Onboarding-PDF (User)
- Permission:
users.access_pdf - Single:
admin/users/access-pdf/{uuid}(inline im neuen Tab) - Bulk:
admin/users/access-pdf-bulk(POST, ZIP mit einer PDF pro User) - Hard limit: maximal 100 User pro Bulk-Request
- Benoetigte Extensions:
ext-zip,ext-dom,ext-mbstring - Bestehende Installationen: Schema-/Permission-Updates als idempotentes SQL-Update ausfuehren
Microsoft SSO (Tenant, Entra ID)
- Zentraler Einstieg:
login(optional mit Tenant-Hintlogin?tenant={tenant-slug}) - OIDC-Endpunkte:
auth/microsoft/startundauth/microsoft/callback - Tenant-Config liegt in
tenant_auth_microsoft - Externe User-Linkung liegt in
user_external_identities - Optionaler Profil-Sync pro Tenant:
sync_profile_on_loginaktiviert Sync bei jedem Microsoft-Loginsync_profile_fields:first_name,last_name,phone,mobile,avatar- Default-Felder bei aktivem Sync ohne Auswahl:
first_name,last_name phone/mobile/avatarnutzen Microsoft Graph (User.Read)
- Shared App Credentials werden in Settings gepflegt (optional Tenant-Overrides)
- Permission fuer Tenant-SSO-Konfiguration:
tenants.sso_manage - Bestandsinstallationen: Schema-/Permission-Updates als idempotentes SQL-Update ausfuehren
- Wichtig:
APP_CRYPTO_KEYin.envsetzen, sonst koennen SSO-Secrets nicht gespeichert/verwendet werden
Asset- und Frontend-System
- Core-CSS-Entrypoint:
/web/css/core.css - CSS-Layer/Vendor-Basis:
/web/css/app-layers.css - Vendor-Overrides:
/web/css/vendor-overrides - Asset-Gruppen:
/config/assets.php - Template-Styles:
renderTemplateStyles('default'|'login') - Seiten-Styles per Action:
Buffer::set('style_groups', ...) - CSS-Contract-Check:
bin/css-contract-check.sh - JS-Bootstrap:
/web/js/app-boot.js(frueh, ohne Module)/web/js/app-init.js(Standardseiten)/web/js/app-login-init.js(Loginseiten)
Qualitaetschecks
Alle Befehle aus dem Projekt-Root ausfuehren.
PHPUnit
docker compose exec php vendor/bin/phpunit --bootstrap vendor/autoload.php
Gezielt:
docker compose exec php vendor/bin/phpunit --bootstrap vendor/autoload.php tests/I18n/TranslationKeysTest.php
docker compose exec php vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Repository/RepoQueryTest.php
PHPStan
docker compose exec php vendor/bin/phpstan analyse -c phpstan.neon --no-progress
Coding Style (PHP CS Fixer)
CI-Check (ohne Dateien zu aendern):
docker compose exec php vendor/bin/php-cs-fixer fix --config=tools/php-cs-fixer/.php-cs-fixer.dist.php --dry-run --diff --verbose
Lokal automatisch formatieren:
docker compose exec php composer cs:fix
Frontend-JS Smoke-Check (ohne Node)
- Browser-DevTools öffnen (Console + Preserve log)
- Kernflows klicken: Users, Address-Book, Audit-Listen, Filter setzen/zurücksetzen
- Keine JavaScript-Errors/Unhandled-Rejections in der Console
Dokumentation
/docs/index.mdDokumentationsstart mit chronologischem Lernpfad und Referenzbereichen/docs/tutorial-02-systemueberblick.mdEinstieg in Produktbild und Kernprinzipien/docs/tutorial-03-setup-und-erster-login.mdlokales Setup und erster Smoke-Test/docs/reference-domain-glossar.mdzentrale Begriffe fuer Tenant/Role/Scope/docs/tutorial-04-architektur-request-flow.mdRequest-Flow und Schichtmodell/docs/tutorial-07-security-tenant-scope.mdSecurity-Grundlagen vor Feature-Arbeit/docs/tutorial-08-api-erweitern.mdverbindlicher API-Aenderungsablauf inkl. OpenAPI
Troubleshooting
- Verhalten wirkt stale nach groesseren Refactors:
docker compose restart php nginx
- Uebersetzungs-Keys fehlen:
docker compose exec php vendor/bin/phpunit --bootstrap vendor/autoload.php tests/I18n/TranslationKeysTest.php
- Favicon/Logo aus Storage fehlt:
- Symlink und Tenant-Kontext pruefen (
web/favicon/tenants -> storage/tenants).
- Symlink und Tenant-Kontext pruefen (
Lizenz
MIT (siehe /LICENSE.md)
Description
Languages
PHP
70.8%
HTML
11.8%
JavaScript
10.4%
CSS
6.6%
Shell
0.3%
Other
0.1%