Dependency Injection

Dependency Injection ist ein Entwurfsmuster (Design Pattern), dass helfen soll, Abhängigkeiten eines Objektes in der objekt-orientierten Programmierung einfacher aufzulösen. Es findet eine Umkehr der Steuerung (Inversion of Control) statt, um das Objekt von unnötigen Verbindungen zu seiner Umwelt zu befreien, die es nur für die Auflösung von Abhängigkeiten, nicht aber für seine eigentliche Aufgabe benötigt. Die Verantwortung für das Auflösen der Abhängigkeiten wird aus dem Objekt in das umliegende Framework, in unserem Falle Extbase, verlagert.

Warum sollten wir Dependency Injection nutzen

Wenn wir ein Objekt (bspw. einen Controller) haben, das eine externe Abhängigkeit (bspw. einen Logger-Service) hat, gibt es verschiedene Wege, diese aufzulösen.

Wir könnten in unserem Objekt einfach t3lib_div::makeInstance('LoggingService'); aufrufen. Das hat aber den Nachteil, dass wir die konkrete Klasse des gewünschten Services angeben müssen. Machen wir dies nun in 30 Objekten und aus unserem LoggingService wird irgendwann ein FileLoggingService, haben wir viel Änderungsarbeit vor uns, da wir alle Einbindungen ändern müssen.

Besser ist es also, dem Objekt per Dependency Injection die korrekte Implementierung geben zu lassen, ohne dass das Objekt überhaupt wissen muss, welche Implementierung das ist. Für die Dependency Injection reicht es, dem Objekt zu sagen, dass es irgendeine Implementierung von LoggingServiceInterface bekommt. An einer zentralen Stelle, kann konfiguriert werden, welche Implementierung genutzt werden soll und das Framework kümmert sich dann darum, die richtige Abhängigkeit zu initialisieren und über eine Inject-Methode in das Objekt zu übergeben.

Wie nutzen wir Dependency Injection in Extbase?

In Extbase wird, wie beim Vorbild FLOW3, die Dependency Injection durch den ObjectManager geregelt*. Jedes Objekt, das per objecktManager->get(), objectManager->create() oder selber per Dependency Injection initialisiert wurde, kann Dependency Injection nutzen.

Per TypoScript kann (im Moment nur systemweit, nicht pro Extension) konfiguriert werden, welche Implementierung genutzt werden soll.

config.tx_extbase.objects.Tx_MyExtension_Service_LoggerServiceInterface.className = Tx_MyExtension_Service_FileLoggerService

Sogar das überschreiben konkreter Klassen in der Dependency Injection ist möglich, wenn der ursprüngliche Autor keine Interfaces verwendet hat. Hierbei ist natürlich besonders darauf zu achten, dass die gewählte Implementierung die benötigten Methoden anbietet.

config.tx_extbase.objects.Tx_MyExtension_Service_DatabaseLoggerService.className = Tx_MyExtension_Service_FileLoggerService

Im Code verwenden wir Inject-Methoden um die von Extbase gelieferten Objekte in Variablen zu speichern.

/**
 * @var Tx_MyExtension_Service_LoggingServiceInterface
 */
protected $loggingService;
 
/**
 * @param Tx_MyExtension_Service_LoggingServiceInterface $loggingService
 * @return void
 */
public function injectLoggingService(Tx_MyExtension_Service_LoggingServiceInterface $loggingService) {
	$this->loggingService = $loggingService;
}

Anhand der Annotation erkennt Extbase, welches Interface implementiert werden soll. Über die TypoScript-Konfiguration wird das konkrete Objekt aufgelöst, initialisiert, auf dem neuen Objekt ebenfalls Dependency Injection ausgeführt und dann die Inject-Methode aufgerufen und das entsprechende Objekt übergeben.

* Die eigentliche Logik der Dependency Injection passiert in der Container-Extension von Daniel Pötzinger. Diese wurde vom Extbase-Team übernommen, abgeändert und direkt in Extbase eingegliedert. Die Logik wird aber durch den ObjectManager abstrahiert und nur dieser ist öffentliche API. Es sollte also auf jeden Fall nur der ObjectManager und nie der ObjectContainer direkt verwendet werden.

Tipps und Tricks

Gibt es für ein Interface nur eine gleichnamige Implementierung, muss man diese nicht konfigurieren. Wenn man also
Tx_MyExtension_Service_LoggerServiceInterface
durch
Tx_MyExtension_Service_LoggerService
auflösen lassen möchte, kann Extbase dies automatisch erkennen und braucht dafür keine TypoScript Konfiguration.

Ausblick

Als Backport "erbt" Extbase all diese Vorgehen natürlich von FLOW3. Das ganze Prinzip des Objekt Managers und der Konfiguration (bei FLOW3 in Objects.yaml) wurde von FLOW3 zurückportiert. Einziger großer Unterschied: FLOW3 benötigt für die Konfiguration nur eine Member-Variable mit einer @inject-Annotation und keine extra inject-Methode.

FLOW3 hat sich aber inzwischen weiterentwickelt und verwendet den ObjectManager nicht mehr. Stattdessen wird der "new"-Operator von PHP genutzt. Die Änderungen dafür sind so umfangreich, dass eine Rückportierung in Extbase im Moment sehr unwahrscheinlich ist.