© DrHitch/Shutterstock.com
Zend Framework 2

2 Dependency Injection und Service Locators im Einsatz


Die von Altmeister Martin Fowler propagierten und zunächst hauptsächlich in der Java- und .NET-Welt verbreiteten Entwurfsmuster Dependency Injection und Service Locators [1] sind nun das Mittel der Wahl, wenn es um die Verwaltung von Abhängigkeiten geht. Und weil das flexible Spiel mit Komponenten und Plug-ins eine der Stärken des Frameworks ist, werden diese bis vor Kurzem unter PHP-Entwicklern noch recht unbekannten Entwurfsmuster nun zu einem zentralen Baustein für das Design von Zend-Framework-Anwendungen.

Das Problem mit der Abhängigkeit

Ein Großteil der strukturellen Änderungen, die Zend Framework 2 erfahren hat, lässt sich auf eine ganz grundlegende Fragestellung zurückführen. Sie ist eigentlich essenziell für objektorientierte Entwicklung, wird doch erst seit einiger Zeit in der PHP-Welt diskutiert: Wie erfüllt man idealerweise die Abhängigkeiten einer Klasse von anderen Klassen, ohne dabei die Erweiterbarkeit und Portabilität zu blockieren? Wann sich diese Frage überhaupt stellt und warum die Antwort darauf letztendlich eine wichtige Designentscheidung in einer Softwarearchitektur ist, wird im Folgenden anhand des eines einfachen Beispiels (Listing 2.1) diskutiert.

<?php
// MyModule/src/MyModule/Queue/Queue.php
namespace MyModule\Queue;

class Queue
{
protected $storage;

public function __construct()
{
$this->storage = new FileStorage();
}

public function next()
{
$jobs = $this->storage->read();
$res = array_pop($jobs);
$this->storage->write($jobs);
return $res;
}

public function append($job)
{
$jobs = $this->storage->read();
$jobs[] = $job;
$this->storage->write($jobs);
}
}

// MyModule/src/MyModule/Queue/FileStorage.php
namespace MyModule\Queue;

class FileStorage
{
protected $filename;

public function __construct()
{
$this->filename = dirname(__FILE__) . '/../../tmp/queue';
}

public function read()
{
$data = @file_get_contents($this->filename);
if (false === $data) {
$data = array();
} else {
$data = unserialize($data);
}
return $data;
}

public function write(array $jobs)
{
file_put_contents($this->filename, serialize($jobs));
}
}

// MyModule/src/MyModule/Controller/QueueController.php
namespace MyModule\Controller;

use \Zend\Mvc\Controller\AbstractActionController;

class QueueController extends AbstractActionController {

public function createJobAction()
{
$queue = new Queue\Queue();

$job = $this->processUserInput();
$queue->appendJob();

return array('job' => $job);

}
// ...
}

Listing 2.1: Implementierung einer Warteschlange

In einer MVC-Webapplikation sollen von einem Controller Aufgaben entgegengenommen werden, die in einer Warteschlange gespeichert und von einem Cron-gesteuerten Skript sequenziell im Hintergrund abgearbeitet werden.

Die Implementierung der Warteschlange ist relativ einfach gehalten: Die Jobs (auf deren genaue Struktur hier nicht weiter eingegangen wird) werden von der Klasse Queue in einem Array verwaltet, mit der Methode append() lässt sich ein weiterer Job hinten anfügen, mit next() der nächste Job abholen und gleichzeitig vom Stapel entfernen. Um den Status über mehrere Aufrufe hinweg speichern zu können, wird eine ebenfalls recht einfach gehaltene Klasse FileStorage verwendet, die PHP-Arrays serialisiert speichern und wieder einlesen kann. Die nötige FileStorage-Instanz wird direkt im Konstruktor von Queue erstellt. Verwendet wird die Warteschlange in diesem Beispiel in der Action-Methode createJobAction() des Controllers QueueController.

Die genaue Implementierung der ominösen Methode processUserInput() ist im Rahmen dieses Kapitels belanglos und wird daher der Fantasie des Lesers überlassen. Wir stellen uns einfach vor, dass darin auf magische Weise die Benutzereingaben in Aufgabenbeschreibungen übersetzt werden, die dann später von dem Cron-Job bearbeitet werden sollen.

Da Queue so eine praktische Klasse ist, soll sie natürlich auch noch in vielen weiteren Projekten verwendet werden. Einige davon erfordern jedoch, dass die Daten in Memcache oder in einer relationalen Datenbank gehalten werden. Doch weil FileStorage direkt im Konstruktor instanziert wird, ist es nicht möglich, die Implementierung einfach auszutauschen, ohne die Klasse Queue anzupassen. Das ist zum einen ein Verstoß gegen das Open Close Principle [2] und eigentlich müsste es für Queue völlig irrelevant sein, wie genau die Daten gespeichert werden. Benötigt wird lediglich die Information, dass es eine (beliebige) Implementierung gibt, um ein Array mittels der Methoden write() zu persistieren und mit read() wieder zu einzulesen.

Dependency Injection

Eine mögliche Auflösung dieses Problems bietet das Entwurfsmuster Dependency Injection (DI). Klassen, die nach diesem Muster entworfen werden, vermeiden, harte Abhängigkeiten herzustellen. Stattdessen bieten sie Konfigurationsmöglichkeiten an, die es der aufrufenden Komponente erlauben, die Implementierungen zur Verfügung zu stellen, die der Situation am besten entsprechen.

Prinzipiell gibt es mehrere Möglichkeiten, die Abhängigkeiten in eine Klasse hinein zu bekommen (Kasten: „Drei Arten der Dependency Injection“). Wohl am häufigsten anzutreffen ist die Übergabe der Abhängigkeiten an den Konstruktor oder die Verwendung von Settern.

Drei ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang