Dependency Injection

 ... oder "wie werde ich die
       Verwandten los"
DI & IoC

Schon mal gehört?
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"
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
Kontrolle? Wer? Wie? Ich?

   Mach mal ein Beispiel ... !
So sieht es häufig aus...

<?php

class Foo {
  private $oDbConn;

    public function __construct() {
      $this->oDbConn = new MyMysqlDriver(CPATH);
    }
}
Besser, aber immer noch gruselig...

<?php

class Foo {
  private $oDbConn;

    private $sDriverClass = MyConfig::getDbClass();

    public function __construct() {
        $this->oDbConn = new $this->sDriverClass();
    }
}
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
Ok, seh ich ein... aber...

DI ist nun so viel besser? Ging bis
         jetzt auch ohne ...
Stimmt ...
Hast Du denn bisher schon mal
     Tests für Deine App
Wenn ja, hast Du eine solche
 Klasse schon mal getestet?
Ja?
Haste bestimmt geflucht, oder?
Wäre Dir mit DI
wahrscheinlich erspart
Genug der Witze. Wie wende
    ich das Muster an?
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) {
        // ...
    }
}
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.
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;
}
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.
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!
Ok, Globals weg ... aber woher
        denn sonst?
Bootstrapping
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!
IoC Container
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
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.
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');
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')
Doch Vorsicht!

auch der IoC Container ist eine
        Abhängigkeit!
Habt ihr Beispiele?

 Lasst uns drüber reden...
Vielen Dank!
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

Dependency injection

  • 1.
    Dependency Injection ...oder "wie werde ich die Verwandten los"
  • 2.
    DI & IoC Schonmal gehört?
  • 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
  • 5.
    Kontrolle? Wer? Wie?Ich? Mach mal ein Beispiel ... !
  • 6.
    So sieht eshäufig aus... <?php class Foo { private $oDbConn; public function __construct() { $this->oDbConn = new MyMysqlDriver(CPATH); } }
  • 7.
    Besser, aber immernoch gruselig... <?php class Foo { private $oDbConn; private $sDriverClass = MyConfig::getDbClass(); public function __construct() { $this->oDbConn = new $this->sDriverClass(); } }
  • 8.
    Was läuft daschief? • 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 ichein... aber... DI ist nun so viel besser? Ging bis jetzt auch ohne ...
  • 10.
  • 11.
    Hast Du dennbisher schon mal Tests für Deine App
  • 12.
    Wenn ja, hastDu eine solche Klasse schon mal getestet?
  • 13.
  • 14.
  • 15.
    Wäre Dir mitDI wahrscheinlich erspart
  • 16.
    Genug der Witze.Wie wende ich das Muster an?
  • 17.
    Zuerst: Was heißtAbhä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'tdo 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: Designby 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: Wegmit 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!
  • 22.
    Ok, Globals weg... aber woher denn sonst?
  • 23.
  • 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!
  • 25.
  • 26.
    Was machen IoCContainer? • 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 ImBootstrap 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 DieserAnsatz 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 Eineexterne Konfigurationsdatei wird verwendet. Hier ein Beispiel aus einer FLOW3 YAML Konfiguration: F3MyPackageFoo: properties: bar: { object: F3MyPackageBarInterface } $oFoo = Container::getInstance()->get('Foo')
  • 30.
    Doch Vorsicht! auch derIoC Container ist eine Abhängigkeit!
  • 31.
    Habt ihr Beispiele? Lasst uns drüber reden...
  • 32.
  • 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