Files
breadcrumb-the-shire/docs/explanation-di-container.md
fs 570e3923b7 refactor(audit): remove factory chain and repository interfaces
Remove AuditRepositoryFactory and AuditServicesFactory — two-layer
factory chain replaced by direct DI container wiring. Delete 4
single-consumer repository interfaces. Register all 4 repositories
and SystemAuditRedactionService directly in container. Update all
service constructors to type-hint concrete classes. Fix architecture
test and documentation references to deleted factories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:33:59 +01:00

4.3 KiB

Dependency Injection Container

Letzte Aktualisierung: 2026-03-06

Was ist das und wozu brauchen wir es?

Statt dass jede Action-Datei ihre Services selbst zusammenbaut (new UserAccountService(new UserRepo(), new AuditRepo(), ...)), gibt es einen zentralen "Service-Speicher": den AppContainer.

Der Container weiß, wie er jeden Service bauen muss — aber er baut ihn erst, wenn er zum ersten Mal gebraucht wird (lazy). Wird der gleiche Service in derselben Anfrage nochmal angefragt, bekommt man dieselbe Instanz zurück (Singleton pro Request).

Services abrufen — der app() Helper

In jeder Action-Datei (pages/**/*.php) steht der Container über die globale Hilfsfunktion app() zur Verfügung:

$userService = app(\MintyPHP\Service\User\UserAccountService::class);
$authService = app(\MintyPHP\Service\Access\AuthorizationService::class);

Den vollständigen Klassennamen (FQN) als Schlüssel verwenden — so gibt es keine Namenskollisionen und IDEs können Autocomplete nutzen.

app() wirft eine RuntimeException wenn der Service nicht registriert ist. Das ist absichtlich laut, damit fehlende Registrierungen sofort auffallen.

Aufbau: Registrars

Beim Start der Anwendung läuft lib/App/registerContainer.php. Es erstellt den Container und ruft 8 Registrars nacheinander auf:

Registrar Was er registriert
RepositoryFactoryRegistrar Alle Repository-Factories (SQL-Schicht)
ServiceFactoryRegistrar Alle Service-Factories (Business-Logik, mit Abhängigkeiten)
AccessRegistrar RBAC-spezifische Services (Roles, Permissions, Gateways)
AuthRegistrar Auth-spezifische Services (Login, SSO, API-Tokens)
DirectoryRegistrar LDAP/Entra-Directory-Services
UserRegistrar User-spezifische Services und Gateways
SettingsRegistrar Settings-Services und Gateways
AppServicesRegistrar Konkrete Top-Level-Services, die direkt in Actions genutzt werden

Jeder Registrar implementiert das Interface ContainerRegistrar::register(AppContainer $container).

Wie Abhängigkeiten aufgelöst werden

// In ServiceFactoryRegistrar:
$container->set(UserServicesFactory::class, static fn (AppContainer $c) =>
    new UserServicesFactory(
        $c->get(AuthServicesFactory::class),      // wird lazy aufgelöst
        $c->get(UserRepositoryFactory::class),
        $c->get(UserGatewayFactory::class),
        $c->get(DatabaseSessionRepository::class)
    )
);

Der Container übergibt sich selbst als $c an die Factory-Funktion — so werden alle Abhängigkeiten automatisch und lazy aus dem Container gezogen. Zirkuläre Abhängigkeiten werden über static fn (): Foo => $c->get(Foo::class) aufgelöst (der Aufruf wird verzögert, bis er wirklich gebraucht wird).

Neuen Service registrieren

1. Service und ggf. Factory erstellen (wie bisher in lib/Service/)

2. Im passenden Registrar eintragen:

// In AppServicesRegistrar (für Services, die direkt in Actions genutzt werden):
$container->set(MeinNeuerService::class, static fn (AppContainer $c) =>
    new MeinNeuerService(
        $c->get(UserServicesFactory::class),
        $c->get(SettingGateway::class)
    )
);

3. In der Action nutzen:

$meinService = app(\MintyPHP\Service\MeinNeuerService::class);

Faustregel: welcher Registrar?

  • Neue Repository-FactoryRepositoryFactoryRegistrar
  • Neue Service-Factory (mit Abhängigkeiten) → ServiceFactoryRegistrar
  • Konkreter Service, der direkt in Actions per app() abgerufen wird → AppServicesRegistrar
  • Klar einer Domain zugeordnet (Auth, Access, Directory, User, Settings) → der passende Domain-Registrar

Wichtige Regeln

  • Nie new SomeService(...) direkt in Action-Dateien schreiben — immer app(SomeService::class) nutzen.
  • Nie Factory-Ketten in Actions (app(SomeFactory::class)->createService()) — den konkreten Service direkt registrieren und per app() holen.
  • Der Container lebt pro HTTP-Anfrage — er wird in web/index.php initialisiert und über setAppContainer() im globalen State abgelegt.
  • In CLI-Skripten (bin/) wird der Container separat initialisiert (s. bin/module-cli-bootstrap.php).
  • In lib/Service/** außerhalb von *Factory.php niemals new ...Factory(), new ...Service() oder new ...Repository() direkt instanziieren.