3. Inversion of Control - IoC
• Paradigma aus dem Software Design / Architektur
• Widerspricht dem "klassischen" Ansatz "jeder besorgt sich
seine Sachen selbst"
• Das Objekt, bzw. die Klasse gibt die Steuerung der
Abhängigkeiten aus der Hand
• Sog. Hollywood-Prinzip: "Don't call us, we call you"
4. Dependency Injection - DI
• Ist ein Entwurfsmuster
• Betrachtet Abhängigkeiten zwischen Objekten
• Verwendet ein erweitertes Factory Entwurfsmuster als Basis
• Ist eine Anwendung des IoC Paradigmas
• Die Anwendung des Musters auf bestehenden Code ist ein
Refactoring Prozess
• Es gibt Bibliotheken in PHP, die am Ende des Prozesses
stehen
6. So sieht es häufig aus...
<?php
class Foo {
private $oDbConn;
public function __construct() {
$this->oDbConn = new MyMysqlDriver(CPATH);
}
}
7. Besser, aber immer noch gruselig...
<?php
class Foo {
private $oDbConn;
private $sDriverClass = MyConfig::getDbClass();
public function __construct() {
$this->oDbConn = new $this->sDriverClass();
}
}
8. Was läuft da schief?
• Die Klasse kümmert sich um das Erzeugen von kritischen
Objekten
• Der Klassenname der Abhängigkeit ist fest in der
instanzierenden Klasse "verdrahtet"
• Selbst beim Auslagern des Klassennamens der
Abhängigkeit kümmert sich dennoch die Klasse selbst um
die Bereitstellung der Instanz
9. Ok, seh ich ein... aber...
DI ist nun so viel besser? Ging bis
jetzt auch ohne ...
17. Zuerst: Was heißt Abhängigkeit?
Eine Abhängigkeit äußert sich durch die Referenzierung oder
Verwendung einer konkreten Implementierung in einer Klasse.
class FooImpl {
public function __construct() {
$oVar = new BarImpl();
}
}
class FooImpl {
public function __construct(BarImpl $oParam) {
// ...
}
}
18. Welche Arten von "Injection" gibt es?
1. Constructor Injection
Instanzen werden über den Konstruktor beim Erstellen der
Instanz übergeben und in der Instanz vorgehalten und / oder
weiter durchgereicht.
2. Getter / Setter Injection
Instanzen werden per setFoo(FooInterface $oFoo) und
getFoo() gesetzt und geholt. Man greift nicht mehr auf die
Eigenschaft direkt zu, da auch die Beschaffung der Instanz
verborgen wird.
19. Schritt 1: Don't do constructor work
Konstruktoren sollen nur den ersten Zustand einer Instanz
beschreiben.
Schlecht:
public function __construct() {
$this->oDb = new DbConnImpl(
"localhost", "user", "pass");
}
Besser:
public function __construct(DbConnImpl $oDb) {
$this->oDb = $oDb;
}
20. Schritt 2: Design by contract
Anstelle der Implementierung sollen Interfaces stehen. Dieser
Schritt löst die Abhängigkeit zu einer konkreten
Implementierung.
statt:
public function setDbConn(MySQLDbConn $oConn) {}
lieber:
public function setDbConn(Queryable $oConn) {}
=> Vorteil: Theoretisch kann die DB nun auch eine SQLite sein,
solang das Interface "Queryable" erfüllt ist.
21. Schritt 3: Weg mit den Globals
Damit sind sowohl globale Variablen, als auch Singletons in
Konstruktoren und Methoden gemeint!
denn:
public function __construct() {
$this->oVar = DbConn::getInstance();
}
ist das Gleiche wie:
public function __construct() {
$this->oVar = $GLOBALS['dbconn'];
}
Es gibt jedoch Ausnahmen, bei denen es Sinn macht einen
Singleton einzusetzen!
24. Aufgaben des Bootstrappings
• Erzeugung der global benötigten Objekte (z. B. DbConn)
• Weiterverteilung der Objekte bei der Initialisierung des
Frameworks
z. B. MVC-Framework
1. Bootstrap erzeugt DbConn
2. Bootstrap erzeugt MVC-Router
3. Bootstrap übergibt DbConn an MVC Router
4. MVC Router findet passenden Controller
5. MVC Router übergibt DbConn an Controller
6. Controller erzeugt Service Layer
7. Controller übergibt DbConn an Service Layer
=> DbConn ist nur ein Mal erzeugt worden!
26. Was machen IoC Container?
• Sie kennen die Abhängigkeiten einer Klasse
• Sie erzeugen Instanzen der geforderten Klasse
• Sie "konfigurieren" die neue Instanz mit den bekannten
Abhängigkeiten
• Manche können Abhängigkeitsgraphen auflösen
=> Klasse A braucht eine Instanz von B
=> B braucht im Konstruktor eine Instanz von C
• Meist erkennen diese dann auch "Circular References"
=> Wenn C im o. g. Beispiel eine Instanz von A benötigen
würde
27. Verschiedene Ansätze: Deklarativ
Im Bootstrap einer App wird der Container konfiguriert. Hierbei
wird die Implementierung für ein Interface angegeben.
$oCont = Container::getInstance()
$oCont->useDep('Queryable', 'MySqlQueryableImpl');
$oCont->manageClass('Foo', 'aNickName');
$oFoo = $oCont->get('aNickName');
assert (($oFoo instanceof Foo) === true) &&
(($oFoo->getConn() instanceof Queryable) === true)
Ein assert auf MySqlQueryableImpl würde ebenfalls
funktionieren, aber gegen das Prinzip verstoßen.
28. Verschiedene Ansätze: Annotation
Dieser Ansatz ist in PHP noch recht neu. Eine Klasse wird per
DocBlock annotiert.
class Foo {
/**
* @inject
*/
public function __construct(Queryable $oConn){
}
}
$oCont = Container::getInstance();
$oCont->setImpl('Queryable', 'MySQLAdapter');
$oFoo = $oCont->get('Foo');
29. Verschiedene Ansätze: Konfiguration
Eine externe Konfigurationsdatei wird verwendet. Hier ein
Beispiel aus einer FLOW3 YAML Konfiguration:
F3MyPackageFoo: properties:
bar: { object: F3MyPackageBarInterface }
$oFoo = Container::getInstance()->get('Foo')
33. Ein paar Links
Blogartikel über Symfony's DIC
http://usrportage.de/archives/926-Dependency-Injection-
Container-Refactorings,-Part-One.html
Martin Fowler über "Injection"
http://martinfowler.com/articles/injection.html
Flow3 Object Framework
http://flow3.typo3.org/documentation/manuals/flow3/
flow3.objectframework/
#flow3.objectframework.objectdependencies