In and out of Control

Teil 2: Mit Dependency Injection Klassenabhängigkeiten kontrollieren
Kommentare

Zwischenfazit: Dependency Injection in seiner ursprünglichen Form ist eine wunderbare Lösung, um implizite Abhängigkeiten explizit zu machen und die Testbarkeit zu erhöhen.

Das geschieht, indem die Kontrolle nach außen gegeben und nach dem Inversion-of-Control-Paradigma gearbeitet wird. Das Ganze hat aber nicht nur Vor-, sondern auch Nachteile. Das Erstellen von Objekten erfordert nun mehr Wissen und Arbeit, da weitere Objekte im Vorfeld instanziiert werden müssen. In Projekten, die eine längere Laufzeit haben, überwiegen die Vorteile aber deutlich.

Implementierungen von DIC

Das Inversion-of-Control-Paradigma ist in PHP noch nicht so lange aktuell. Mit den steigenden nich funktionalen Anforderungen an PHP-Webapplikationen wie Testbarkeit, Wiederverwendbarkeit und Wartbarkeit rückt das Thema immer mehr in den Fokus der Entwickler und Maintainer der großen PHP-Frameworks. Dabei hat sich die DI über einen Dependency-Injection-Container (DIC) als Lösung für die genannten Anforderungen etabliert. Die Implementierungen von DICs gebräuchlicher Frameworks funktionieren sehr ähnlich, sind aber an die Eigenheiten des jeweiligen Frameworks angepasst, was sich zum Beispiel in der Art, wie der DIC konfiguriert wird, zeigt. Alle Frameworks bieten eine Konfiguration über Konfigurationsdateien (meist XML oder YML, in einigen Fällen auch PHP), Annotationen oder über einen Automatismus an. Listing 4 zeigt die Konfiguration/Implementierung des Dependency-Injection-Containers. Die Arbeit, die zwischen der Ausgangskonfiguration und dem Ziel der Instanziierung der Klasse B mit allen Abhängigkeiten steht, soll der DIC des jeweils eingesetzten Frameworks übernehmen.

Listing 4

namespace BsNamespace;

class B
{
  private $a;

  public function __construct(A $a)
  {
    $this->a = $a;
  }

  ...
}

namespace CsNamespace;

class C
{
  ...
}

namespace AsNamespace;

class A
{
  private $c;  

  public function __construct(C $c)
  {
    $this->c = $c;
  }
   
  ...
}

Ziel:
$b  = $dic->get('B');

Symfony2

Die Aufgabe würde in einer reinen Symfony2-Umgebung so gelöst, dass in einer Konfigurationsdatei die Abhängigkeiten aufgelöst würden. Das geschieht entweder in der projektspezifischen Konfigurationsdatei config.yml/xml/php oder innerhalb eines Bundles. Dabei besteht die Möglichkeit, diese Konfiguration aus dem Bundle per Import in die projektspezifische Konfigurationsdatei zu laden oder „automagisch“ über den Bootstrap-Prozess von Symfony dem DIC bekannt zu geben. Diese „Magie“ wird über die Erstellung einer ContainerExtension-Klasse erreicht. Listing 5 zeigt die einfachste Möglichkeit, die Abhängigkeiten zu konfigurieren. Ein Aufruf von $dic->get(‚B‘); zur Instanzierung der Klasse B mit allen Abhängigkeiten ist damit einfach möglich. Symfony2 erlaubt die Steuerung der Sichtbarkeit der Klassen, die über den DIC konfiguriert sind. Sie können als public oder private markiert werden. Als public markierte Klassen können im gesamten Projekt verwendet werden, als private markierte Klassen können hingegen nur von anderen Klassen als Parameter genutzt werden. Zur Konfiguration der Abhängigkeiten über Annotationen muss eine Erweiterung (Bundle) installiert werden: das JMSDiExtraBundle. Diese Erweiterung ist sehr hilfreich bei der Konfiguration der Abhängigkeiten in Controllern.

Listing 5: app/config.yml

parameters:
    my_class_a.class: AsNamespaceA;
    my_class_b.class: BsNamespaceB;
    my_class_c.class: CsNamespaceC;


services:
    my_class_b:
        class:        %my_class_b.class%
        arguments:    [@my_class_a]
my_class_a:
        class:        %my_class_a.class%
        arguments:    [@my_class_c]
my_class_c:
        class:        %my_class_c.class%

TYPO3 Flow

In Flow3 können Abhängigkeiten konfiguriert oder automagisch aufgelöst werden. Diese automagische Auflösung nennt Flow3 Autowiring. Dabei „schaut“ der Autowiring-Mechanismus auf den Typ der Parameter und injiziert sie automatisch zur Laufzeit. Zur Nutzung dieses Features muss in Flow3 keine weitere Konfiguration mehr vorgenommen werden. Um die Instanziierung der Klasse B zu erreichen, muss mit dem Autowiring-Feature nichts weiter getan werden als new B() aufzurufen. Falls gewünscht, kann der DIC von Flow3 aber auch manuell über eine YAML-Datei konfiguriert werden. Dazu wird die Objects.yaml angepasst und für die Klassen werden, ähnlich wie bei Symfony2, Abschnitte definiert. Bei der Verwendung dieser Konfiguration kann das Autowiring auch abgeschaltet werden.

Listing 6

namespace BsNamespace;


class B
{
  private $a;

  public function __construct(AsNamespaceA $a)
  {
    $this->a = $a;
  }

  ...
}

namespace CsNamespace;

class C
{
  ...
}

namespace AsNamespace;

class A
{
  private $c;  

  public function __construct(CsNamespaceC $c)
  {
    $this->c = $c;
  }
   
  ...
}

Pimple

Der DIC Pimple ist der wahrscheinlich einfachste Dependency-Injection-Container für PHP 5.3. Er besteht aus einer PHP-Datei (Pimple.php) mit nur 149 Zeilen, wovon ein erheblicher Anteil Dokumentation ist, und ist ein Beispiel für das „Gut-genug-Prinzip“, weil seine Features für viele Projekte ausreichend und sehr einfach zu nutzen sind (Listing 7). Mit Pimple ist es nun ganz einfach möglich, das Objekt B zu bekommen: $b = $container[‚B‘]; . Es erlaubt auch die Wiederverwendung von Objekten. Im Beispiel wird für jeden Aufruf von B ein neues B-Objekt erzeugt. Mit einer etwas anderen Konfiguration der Containerobjekte wird das verhindert. Dazu muss einfach nur folgendes verändert werden (siehe zweite Code-Box):

Listing 7

//PimpleConfig.php
require_once '/path/to/Pimple.php';

$container = new Pimple.php;

//define some parameters
$container['class_name_a'] = 'A';
$container['class_name_c'] = 'C';

//define the objects
$container['C'] = function ($c) {
  return new $container['class_name_c']();
};


$container['A'] = function ($c) {
  return new $container['class_name_a']($c['C']);
};


$container['B'] = function ($c) {
  return new B($c['A']);
};
//PimpleConfig.php

...

//define the objects
$container['C'] = $container->share(function ($c) {
  return new $container['class_name_c']();
};

...

Weiter mit: Teil 3

Alle Teile: Teil 1, Teil 2, Teil 3

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -