Menu
Coddy logo textTech

Dependency Injection

Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy — Lektion 57 von 91.

In der vorherigen Lektion haben Sie gesehen, wie Komposition es ermöglicht, dass ein Car ein Engine-Objekt enthält. Aber es gibt ein Problem beim Erstellen von Abhängigkeiten innerhalb der Klasse – es macht den Code starr und schwer zu testen. Dependency Injection löst dies, indem Abhängigkeiten von außen übergeben werden, anstatt sie intern zu erstellen.

Vergleichen Sie diese beiden Ansätze:

<?php
// Ohne DI - Abhängigkeit wird intern erstellt
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // Fest gekoppelt
    }
}

// Mit DI - Abhängigkeit wird übergeben
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // Von außen injiziert

Der zweite Ansatz ist Dependency Injection. Die Klasse Car erstellt ihren Motor nicht selbst – sie erhält einen. Diese einfache Änderung hat enorme Vorteile: Sie können verschiedene Motortypen übergeben, Implementierungen für Tests austauschen und die Klasse wird flexibler.

Für noch mehr Flexibilität injizieren Sie Interfaces anstelle von konkreten Klassen:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

Jetzt funktioniert Car mit jeder Klasse, die EngineInterface implementiert – einem Benzinmotor, einem Elektromotor oder einem Mock zum Testen. Diese Entkopplung ist der Grund, warum Dependency Injection grundlegend für das Schreiben von wartbaren, testbaren PHP-Anwendungen ist.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Benachrichtigungssystem bauen, das die Vorteile von Dependency Injection demonstriert. Anstatt fest zu kodieren, wie Nachrichten gesendet werden, erstellen Sie ein flexibles System, bei dem die Zustellungsmethode einfach ausgetauscht werden kann.

Sie werden Ihren Code über vier Dateien organisieren:

  • MessageSenderInterface.php — Definieren Sie ein Interface namens MessageSenderInterface mit einer einzigen Methode send(string $recipient, string $message): string. Dieses Interface legt den Vertrag fest, dem jeder Nachrichtensender folgen muss.
  • EmailSender.php — Erstellen Sie eine Klasse EmailSender, die MessageSenderInterface implementiert. Binden Sie die Interface-Datei ein. Die Methode send() sollte "Email to $recipient: $message" zurückgeben.
  • NotificationService.php — Erstellen Sie eine Klasse NotificationService, die ihre Abhängigkeit über den Konstruktor erhält. Binden Sie die Interface-Datei ein. Die Klasse sollte:
    • Ein MessageSenderInterface in ihrem Konstruktor mittels Constructor Promotion akzeptieren
    • Eine Methode notify(string $recipient, string $message) haben, die den Aufruf an den injizierten Sender delegiert und dessen Ergebnis zurückgibt
    Beachten Sie, wie NotificationService vom Interface abhängt und nicht von einer konkreten Klasse. Das bedeutet, dass Sie jeden Sender injizieren könnten, der das Interface implementiert, ohne den Service zu ändern.
  • main.php — Binden Sie die Dateien EmailSender und NotificationService ein. Sie erhalten zwei Eingaben: einen Empfänger und eine Nachricht. Erstellen Sie einen EmailSender, injizieren Sie ihn in einen NotificationService, rufen Sie dann notify() auf und geben Sie das Ergebnis aus.

Dieses Muster hält Ihren NotificationService flexibel — er funktioniert mit jedem Sender, den Sie injizieren, egal ob es sich um E-Mail, SMS oder etwas handelt, das Sie noch nicht gebaut haben. Der Service erstellt seine Abhängigkeiten nicht selbst; er erhält sie von außen.

Spickzettel

Dependency Injection (DI) ist eine Technik, bei der Abhängigkeiten von außen in eine Klasse übergeben werden, anstatt intern erstellt zu werden.

Ohne DI (stark gekoppelt):

<?php
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // Intern erstellt
    }
}

Mit DI (lose gekoppelt):

<?php
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // Von außen injiziert

Für maximale Flexibilität sollten Interfaces anstelle von konkreten Klassen injiziert werden:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

Dies ermöglicht es Car, mit jeder Klasse zu arbeiten, die EngineInterface implementiert, wodurch der Code wartbarer und testbarer wird.

Probier es selbst

<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';

// Eingabe lesen
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));

// TODO: Eine EmailSender-Instanz erstellen
// TODO: In einen NotificationService injizieren
// TODO: notify() aufrufen und das Ergebnis ausgeben

?>
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung