Artikelformat

Dependency Injection (Teil 4)

Heute kommen wir zum Abschluß des Dependency Injection Themas. Nachdem 3 Konzepte vorgestellt wurde, gibt es heute 3 Frameworks, die diese Konzepte umsetzen. Die sind zum Teil ersetzbar, aber es geht hier auch darum Alternativen aufzuzeigen. Es schließt sich somit der Kreis und die Unklarheiten der Konzeptartikel werden aufgeklärt.

Bevor wir die einzelnen Frameworks angehen gibt es noch eine kleine Anpassung die durchgeführt wurde. Wer die Quelltexte zum selber Spielen herunterlädt wird sich sonst wohl etwas wundern. Die MyDatabase Klasse wurde so angepasst, dass beim Aufruf der query-Methode eine Ausgabe auf die Kommandozeile erfolgt. Da keine echte Logik in der Klasse verbaut wurde, ist dies eine Möglichkeit den Aufruf zu verfolgen. Da es ein PoC ist, wurde ein einfaches echo benutzt. Im Produktivcode kann man dann auch einen schönen Logger einbauen. Weiter gibt die Methode nun true zurück. Dies soll ein erfolgreiches Absetzen des Statements simulieren.

In der Example-Klasse wird entsprechend auf das true reagiert und eine entsprechende Log-Meldung ausgegeben. Hiermit kann man sicherstellen, dass die Kombination der beiden Klassen auch wirklich funktioniert hat. Denn nur dann werden beide Logmeldungen rausgeschrieben. Und noch mal sei erwähnt: Es ist ein PoC.

Nun kommen wir aber zur Implementierung.

Constructor Injection:
Für dieses Konzept wurde Yadif (Yet another Dependency Injection Framework) benutzt. Das Framework ist recht übersichtlich und besteht aus einer Container-Klasse für den DI-Container und einer Exception Klasse. Unsere PHP-Klassen mit dem Beispiel-Code sind bereits aus Teil 1 bekannt und daher müssen wir uns nur die Konfiguration und Benutzung von Yadif anschauen. Diese sieht in PHP so aus:

$configuration = array(
	'Example' => array(
		'class' => 'Example',
		'arguments' => array(
			'__construct' => array('DB')
		)
	),
	'DB' => array('class' => 'MyDatabase')
);

$container = new Yadif_Container($configuration);

$example = $container->getComponent('Example');
$example->getSomething();

Die Konfiguration des Containers ist hier als verschachteltes Array angelegt. Den Container selbst müsste man noch in ein Singleton packen, damit man das eigentliche Problem nicht nur verschiebt sondern löst. Aber hier ist der Vorteil, dass man in einem Testfall den Produktiv-Container nimmt und die Configuration on-the-fly mit einer replace-Methode so anpassen kann, dass die Abhängigkeiten im Testfall durch Mocks ersetzen werden.

Ansonsten sieht man nur noch wie eine Klasse über den Container instaniiert wird und als Beispiel wird die getSomething-Methode aufgerufen, die ja den Zugriff auf die Datenbank simuliert.

Setter Injection:
Das Setter Injection Konzept wurde in Teil 2 dieser Serie erklärt und als Beispielframework schauen wir die Implementierung des Symfony-Frameworks an. Diese ist in den Symfony-Components unabhängig vom Gesamtframework verfügbar. Konfiguriert sieht dies im Code so aus:

$sc = new sfServiceContainerBuilder();

$sc->register("database", "MyDatabase");
$sc->register("example", "Example")->addMethodCall('setDatabase', array(new sfServiceReference('database')));

$example = $sc->example;
$example->getSomething();

Zuerst wird ein Container erzeugt, in diesem wird mit dem Namen „database“ die Klasse MyDatabase verknüpft. Das passiert ebenfalls mit der Example-Klasse. Diese bekommt aber noch die Information, dass beim initialisieren der Klasse die Methode setDatabase aufgerufen werden soll und dort die Klasse, die unter dem Namen database zu finden ist als Parameter dient.

Hier werden die Magic Methods genutzt, um an die initialisierte Klasse hinter example zu gelangen. Und der Aufruf von getSomething ist bereits bekannt.

Interface Injection:
Das Interface Injection Konzept wurde hier ja sehr kontrovers diskutiert und es wurde auch in Frage gestellt, ob das Konzept in der PHP-Welt überhaupt funktionieren kann. Diese Frage hat sich Benjamin Eberlei auch gestellt und ein PoC implementiert. Das hilft mir natürlich ungemein und daher nutze ich seine Implementierung als Bibliothek:

Whitewashing_Container::registerComponent('InjectDBAccess', new MyDatabase());
$example = Whitewashing_Container::load('Example');

$example->getSomething();

Um diese Konstruktion zum Laufen zu bewegen muss man noch eine kleine Anpassung im Code vornehmen. Denn die Methode, die InjectDBAccess nutzt für das Injizieren der Abhängigkeit muss gleich dem Klassen-Namen sein. Das ist aber nur eine Feinheit, die man mit etwas Motivation in der Bibliothek anpassen kann.

Es wird im Container registriert welche Klasse benutzt wird, wenn das entsprechende Interface gefunden wird. D.h. Example muss in unserem Beispiel InjectDBAccess implementieren und darum wird die Klasse MyDatabase injiziert, da das InjectDBAccess Interface mit MyDatabase verknüpft ist.

Anschließend wird eine Instanz der Example-Klasse aus dem Container genommen und wie zuvor getSomething aufgerufen wird.

Man kann hier sehr gut erkennen, dass die Abhängigkeiten der Klassen durch die Interfaces definiert werden und man daher eine übersichtlichere Konfiguration des Containers vorfindet.

2 Kommentare

  1. Schöner Post. Für mich wird hier wieder einmal schön aufgezeigt, dass wenn man das Prinzip verstanden hat, die Anwendung des Konzeptes dann relativ einfach ist.

    Antworten
  2. Christian Weiss

    18/07/2011 @ 12:15

    Wenn das injizierte Object von der Mehrzahl der Klassen-Methoden verwendet wird, also eine hohe Kohärenz zwischen injizierten Object und Klassen-Methoden besteht, kann es als mandatory angesehen werden. Dann ist eine injection via Konstruktor m.E. sehr angebracht. Der Konstruktor sollte dann die injectDbAccess() aufrufen und das injizierte Object dahin übergeben. Klar auch hier sollte die Klasse das Interface (IfDbAccess) implementieren. Doof ist nur dass im dem __construct() auf diesem Weg „vergessen kann“ ein passendes Type Hinting zu geben. Spätestens die injectDbAccess(), die sich an den Interface-Vertrag hält, wird hier aber motzen. Schlimmsten Falls, erfolgt das Gemäckere etwas später als in der Idealvorstellung. Da die inject-Interfaces aber von diversen Klassen implementiert werden soll (100 Klassen die zum den Logger injiziert bekommen sollen), hat der Konstructor im Interface nichts zu suchen.

    Ob über __construct() oder via injectSOMETHING() – mir stellt sich die Frage, ob das Injizieren nicht konzeptionell ein einmaliger Vorgang ist – es also ein guter Style ist sicherzustellen, das nur einmal injiziert werden kann.

    Also bei der Verwendung via __construct() die injectDbAccess() als privat / protected zu definieren? Bzw. bei der Implementierung bei der klassischen Verwendung als Setter das Über-Injizieren (Überdosis) vermeiden:

    if (!is_null($this->_injectedDbAccess) {
    throw new Exception(‚Do not incejt twice.‘);
    }

    $this->_injectedDbAccess = $db

    Dann sollte die Methode natürlich entsprechen umbenannt werden, damit dieser Umstand deutlich wird:
    injectDbAccessOnce(IfDbAccess $db) {}

    Bin gespannt auf Eure Meinung.

    Antworten

Schreibe einen Kommentar

Pflichtfelder sind mit * markiert.