Dependency Injection in PHP: in der Praxis
Dependency Injection (DI) ist eines der Schlüsselprinzipien von SOLID, das es ermöglicht, flexiblen, testbaren und wartbaren Code zu schreiben. In diesem Artikel werden wir anhand realer Beispiele durchgehen, wie man Abhängigkeitsinjektion in PHP umsetzt, ohne abstrakte Theorien. Sie erfahren, wie Sie starre Kopplungen zwischen Klassen vermeiden und Ihren Code änderungsbereit machen.
Was ist Dependency Injection und warum wird sie benötigt?
Stellen Sie sich vor, Sie haben eine Klasse UserController, die mit einer Datenbank arbeitet. Ohne DI erstellen Sie das Verbindungsobjekt wahrscheinlich direkt innerhalb des Controllers:
class UserController { private $db; public function __construct() { $this->db = new MySQLConnection('localhost', 'user', 'pass'); }}Dieser Ansatz erzeugt eine starre Kopplung (tight coupling). Wenn Sie auf PostgreSQL umsteigen oder ein Mock-Objekt für Tests verwenden möchten, müssen Sie den Controller-Code ändern. Dependency Injection löst dieses Problem: Abhängigkeiten werden von außen übergeben, nicht innerhalb der Klasse erstellt.
Die Hauptvorteile von DI:
- Reduzierung der Code-Kopplung
- Vereinfachung des Testens (Abhängigkeiten können leicht ausgetauscht werden)
- Wiederverwendbarkeit von Klassen
- Saubere Architektur und Einhaltung des Prinzips der Abhängigkeitsumkehr (DIP)
Methoden der Abhängigkeitsinjektion in PHP
Es gibt drei Hauptmethoden der Abhängigkeitsinjektion. Betrachten wir jede mit Beispielen.
1. Injektion über den Konstruktor
Die gebräuchlichste und empfohlene Methode. Abhängigkeiten werden über die Konstruktorparameter übergeben und in den Eigenschaften der Klasse gespeichert.
<?phpinterface DatabaseInterface { public function query(string $sql): array;}
class MySQLDatabase implements DatabaseInterface { public function query(string $sql): array { // Reale Logik für die Arbeit mit MySQL return ['id' => 1, 'name' => 'John']; }}
class UserRepository { private DatabaseInterface $db; public function __construct(DatabaseInterface $db) { $this->db = $db; } public function getUser(int $id): array { return $this->db->query("SELECT * FROM users WHERE id = $id"); }}
// Verwendung:$db = new MySQLDatabase();$repo = new UserRepository($db);$user = $repo->getUser(1);?>Beachten Sie: Wir verwenden das Interface DatabaseInterface. Dies ermöglicht es, die Implementierung leicht auszutauschen – zum Beispiel gegen einen Test-Stub.
2. Injektion über Setter
Wird verwendet, wenn die Abhängigkeit zum Zeitpunkt der Objekterstellung nicht zwingend erforderlich ist oder sich während der Laufzeit ändern kann.
<?phpclass MailService { private ?MailerInterface $mailer = null; public function setMailer(MailerInterface $mailer): void { $this->mailer = $mailer; } public function send(string $to, string $subject, string $body): void { if ($this->mailer === null) { throw new \\RuntimeException('Mailer not configured'); } $this->mailer->send($to, $subject, $body); }}
// Verwendung:$mailService = new MailService();$mailService->setMailer(new SmtpMailer());$mailService->send('user@example.com', 'Hello', 'Body');?>Nachteil dieses Ansatzes: Sie garantieren nicht, dass die Abhängigkeit vor dem Aufruf der Methode send() gesetzt ist. Daher ist für obligatorische Abhängigkeiten der Konstruktor vorzuziehen.
3. Injektion über die Methode
Die Abhängigkeit wird direkt an die Methode übergeben, in der sie benötigt wird. Nützlich für seltene oder kontextbezogene Abhängigkeiten.
<?phpclass ReportGenerator { public function generate(DataFetcherInterface $fetcher, string $reportType): array { $data = $fetcher->