Factory
PSR-11 Container mit dreistufiger Auflösung, von Pre-Registration über Backend-Delegation bis Reflection.
Einführung
Dependency Injection Container gibt es viele. Die meisten erfordern umfangreiche Konfiguration, YAML-Dateien, PHP-Definitionen, Annotations. Für ein Platform-Package wie Jardis ist das zu viel Overhead: Die meisten Services brauchen keine Konfiguration, manche werden manuell verdrahtet, und nur wenige leben in einem externen Container.
jardissupport/factory löst genau dieses Problem mit einer dreistufigen Auflösungskette: Zuerst werden vorab registrierte Instanzen geprüft, dann ein optionaler Backend-Container (Symfony, Laravel, PHP-DI), und als Fallback instanziiert die Factory die Klasse per Reflection selbst. Kein YAML, keine Annotations, keine Magie, drei Dateien, ein Interface.
- PSR-11 kompatibel — funktioniert überall wo
ContainerInterfaceerwartet wird - Dreistufige Auflösung — Instances → Backend → Reflection
- Null Konfiguration — Klassen ohne required Parameter werden automatisch instanziiert
- Backend-Integration — Symfony, Laravel oder jeder andere PSR-11 Container als Fallback
- Immutable — nach dem Konstruktor nicht änderbar, readonly Properties
Installation
composer require jardissupport/factoryGitHub: jardisSupport/factory
Grundlegende Nutzung
Einfachster Fall — Reflection
use JardisSupport\Factory\Factory;
$factory = new Factory();
// Klassen ohne required Parameter werden automatisch instanziiert
$service = $factory->get(SimpleService::class);Pre-Registration
$factory = new Factory(instances: [
LoggerInterface::class => $psrLogger,
CacheInterface::class => $cacheService,
'app.config' => ['debug' => true],
]);
$logger = $factory->get(LoggerInterface::class); // $psrLogger
$config = $factory->get('app.config'); // ['debug' => true]null und false sind gültige Werte, die Prüfung erfolgt per array_key_exists, nicht isset.
Mit Backend-Container
// Symfony-Container als Fallback
$factory = new Factory(
container: $symfonyContainer,
instances: [
CacheInterface::class => $customCache, // überschreibt Symfony
],
);Instances haben immer Vorrang vor dem Backend-Container.
Auflösungskette
get() prüft drei Stufen, die erste die trifft, gewinnt:
get('MyService')
├── 1. Instances[$id] vorhanden? → Instanz zurückgeben
├── 2. Backend-Container->has($id)? → Backend->get($id)
└── 3. class_exists($id)? → new $id() per Reflection
└── Keine Stufe trifft? → NotFoundExceptionStufe 3 — Reflection
Die Reflection-Stufe instanziiert Klassen nur wenn sie keine required Konstruktor-Parameter haben:
// Funktioniert — kein Konstruktor oder nur optionale Parameter
class SimpleService {}
class ConfigurableService {
public function __construct(private string $name = 'default') {}
}
// Wirft ContainerException — required Parameter
class DatabaseService {
public function __construct(private PDO $pdo) {}
}Für Klassen mit required Parametern: create() verwenden oder als Instance registrieren.
create() — Explizite Instanziierung
create() umgeht die Auflösungskette komplett und instanziiert per Reflection mit Parametern:
// Mit Parametern
$command = $factory->create(ProcessOrder::class, $orderId, $userId);
// Ohne Parameter (wie get(), aber bypassed Instances und Backend)
$service = $factory->create(SimpleService::class);get() vs. create()
get($id) | create($class, ...$params) | |
|---|---|---|
| Prüft Instances | Ja | Nein |
| Prüft Backend | Ja | Nein |
| Akzeptiert Parameter | Nein | Ja |
| Neue Instanz garantiert | Nur bei Reflection | Immer |
Fehlerbehandlung
| Exception | Ursache | PSR-11 Interface |
|---|---|---|
NotFoundException | Keine der drei Stufen findet $id | NotFoundExceptionInterface |
ContainerException | Klasse hat required Parameter oder existiert nicht | ContainerExceptionInterface |
use JardisSupport\Factory\NotFoundException;
use JardisSupport\Factory\ContainerException;
try {
$service = $factory->get('unknown.service');
} catch (NotFoundException $e) {
// "Entry "unknown.service" not found in container."
}
try {
$db = $factory->get(DatabaseService::class);
} catch (ContainerException $e) {
// "Class DatabaseService has required constructor parameters.
// Register it as instance or use create()."
}Architektur
Drei Dateien, bewusst minimal:
src/
├── Factory.php ← PSR-11 Container (Orchestrator)
├── ContainerException.php ← Technische Fehler
└── NotFoundException.php ← ID nicht gefundenDie Factory ist immutable. $instances und $container sind readonly Properties. Kein register(), kein set() nach der Konstruktion.
API-Referenz
Factory
| Methode | Signatur | Beschreibung |
|---|---|---|
get | get(string $id): mixed | Dreistufige Auflösung (PSR-11) |
has | has(string $id): bool | Prüft ob $id auflösbar wäre (PSR-11) |
create | create(string $className, mixed ...$params): object | Direkte Instanziierung per Reflection |
Vollständiges Beispiel
use JardisSupport\Factory\Factory;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
// Services vorbereiten
$logger = new FileLogger('/var/log/app.log');
$cache = new RedisCache($redis);
$pdo = new PDO('mysql:host=localhost;dbname=shop', 'root', '');
// Factory konfigurieren
$factory = new Factory(instances: [
LoggerInterface::class => $logger,
CacheInterface::class => $cache,
PDO::class => $pdo,
]);
// Auflösung
$factory->get(LoggerInterface::class); // $logger (Stufe 1: Instance)
$factory->get(SimpleValidator::class); // new SimpleValidator() (Stufe 3: Reflection)
// Explizite Erstellung mit Parametern
$handler = $factory->create(
OrderHandler::class,
$factory->get(PDO::class),
$factory->get(CacheInterface::class),
);