107 lines
4.3 KiB
Markdown
107 lines
4.3 KiB
Markdown
# Dependency Injection Container
|
|
|
|
## 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 `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
|
|
|
|
```php
|
|
// In ServiceFactoryRegistrar:
|
|
$container->set(UserServicesFactory::class, static fn (AppContainer $c) =>
|
|
new UserServicesFactory(
|
|
$c->get(AuditServicesFactory::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:**
|
|
|
|
```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-Skripten (`bin/`) wird der Container separat initialisiert (s. `bin/scheduler-run.php`).
|
|
- In `lib/Service/**` außerhalb von `*Factory.php` niemals `new ...Factory()`,
|
|
`new ...Service()` oder `new ...Repository()` direkt instanziieren.
|