Files
breadcrumb-the-shire/docs/explanation-di-container.md

109 lines
4.3 KiB
Markdown
Raw Permalink Normal View History

2026-03-04 15:56:58 +01:00
# Dependency Injection Container
Letzte Aktualisierung: 2026-03-06
2026-03-04 15:56:58 +01:00
## 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:
```php
$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 `core/App/registerContainer.php`.
2026-03-04 15:56:58 +01:00
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
```php
// In ServiceFactoryRegistrar:
$container->set(UserServicesFactory::class, static fn (AppContainer $c) =>
new UserServicesFactory(
$c->get(AuthServicesFactory::class), // wird lazy aufgelöst
2026-03-04 15:56:58 +01:00
$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 `core/Service/`)
2026-03-04 15:56:58 +01:00
**2. Im passenden Registrar eintragen:**
```php
// 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:**
```php
$meinService = app(\MintyPHP\Service\MeinNeuerService::class);
```
**Faustregel: welcher Registrar?**
- Neue *Repository-Factory*`RepositoryFactoryRegistrar`
- 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-Commands wird der Container separat initialisiert (über die Runtime-Bootstrap-Klassen unter `core/Console/Support/`).
- In `core/Service/**` außerhalb von `*Factory.php` niemals `new ...Factory()`,
2026-03-04 15:56:58 +01:00
`new ...Service()` oder `new ...Repository()` direkt instanziieren.