Inversion of Control – Teil 3 – Dependency Injection im Symfony 2.0 Framework

Posted by in Allgemein, Pattern

So da nun auch Teil 3 auf PHP hates me veröffentlicht wurde (danke nochmal an Nils) hier nun auch nochmal die Veröffentlichung in meinem Blog 😉

Nachdem ich nun an einem (zugegebenermaßen unschönen) Beispiel gezeigt habe, wie so ein Container funktioniert möchte ich den Container aus dem Symfony Framework vorstellen. Zend hatte mal einen Container im Programm (zumindest im Labratory) jedoch wurde dieser wieder verworfen. Die genauen Hintergründe sind mir leider nicht bekannt. Ansonsten gibt es noch den einen oder anderen Container für PHP, jedoch macht Symfony mir in diesem Bereich den besten Eindruck. Von der Stabilität als auch von der Geschwindigkeit her. Er ist mit einer Code-Coverage von 100% getestet (das sagt noch nicht alles aus, aber schonmal, dass man da wirklich dahinter ist!). Und ansonsten lassen sich alle Konfigurationen und Zusammenstellungen zu 100% als PHP darstellen und kompilieren. Man kann also bei Bedarf die Unterstützung eines PHP-Opcode-Caches voll ausnutzen.

Auch hier wird ein Container verwendet. Das tolle ist aber, dass sich jeder aussuchen kann wie er seinen Container konfigurieren möchte. Man kann ihn direkt als PHP programatisch beschreiben. Oder mit INI-Dateien, mit XML-Dateien oder als YAML-Dateien. Und wenn der Container geladen ist, kann man ihn auch in jedes dieser Formate abspeichern egal wie man ihn geladen hat. Man kann so also wenn man maximale Performance will in der Entwicklung mit XML arbeiten und auf dem Live-System eine gespeicherte PHP-Konfiguration deployen.

Zuerst einmal wie eine Konfiguration von Hand ohne DI aussehen würde:

$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
  'auth'     => 'login',
  'username' => 'foo',
  'password' => 'bar',
  'ssl'      => 'ssl',
  'port'     => 465,
));
 
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);

Und hier als Beispiel wir er im Symfony als XML konfiguriert wird:

<?xml version="1.0" ?>
 
<container xmlns="http://symfony-project.org/2.0/container">
  <parameters>
    <parameter key="mailer.username">foo</parameter>
    <parameter key="mailer.password">bar</parameter>
    <parameter key="mailer.class">Zend_Mail</parameter>
  </parameters>
  <services>
    <service id="mail.transport" class="Zend_Mail_Transport_Smtp"
shared="false">
      <argument>smtp.gmail.com</argument>
      <argument type="collection">
        <argument key="auth">login</argument>
        <argument key="username">%mailer.username%</argument>
        <argument key="password">%mailer.password%</argument>
        <argument key="ssl">ssl</argument>
        <argument key="port">465</argument>
      </argument>
    </service>
    <service id="mailer" class="%mailer.class%">
      <call method="setDefaultTransport">
        <argument type="service" id="mail.transport" />
      </call>
    </service>
  </services>
</container>

Man beachte, dass hier auch Variablenersetzung verwendet wird. So kann man den Benutzernamen auch mehrfach verwendet und dann schnell für alle Klassen ändern.

So wie wird der Spaß nun aber verwendet? Ganz einfach. Zuerst wird der Container erzeugt und mit der XML-Datei befüllt.

require_once '/PATH/TO/sfServiceContainerAutoloader.php';
sfServiceContainerAutoloader::register();
 
$sc = new sfServiceContainerBuilder();
 
$loader = new sfServiceContainerLoaderFileXml($sc);
$loader->load('/somewhere/container.xml');

Wenn man den Container nun als PHP-Datei speichern will um ihn schneller zu laden geht das wie folgt:

$dumper = new sfServiceContainerDumperPhp($sc);
$code = $dumper->dump(array('class' =>'Container'));
 
file_put_contents('/somewhere/container.php', $code);

Das ist aber nur optional.

Zugriff auf die Objekte holt man sich nun über:

$transport = $sc->getMailTransportService();
$mailer = $sc->getMailerService();

Ein ebenfalls sehr nettes Feature ist, dass man den Container auch als .dot-Datei ausgeben kann und mit dem dot-Programm in ein PNG-Bild konvertieren kann. Auf diese Weise hat man schnell einen Überblick über seine bestehende Anwendung und kann dies auch für die Dokumentation verwenden.

$dumper = new sfServiceContainerDumperGraphviz($sc);
file_put_contents('/somewhere/container.dot', $dumper->dump());
$ dot -Tpng /somewhere/container.dot > /somewhere/container.png

So damit hoffe ich nun einen guten Einblick in das Feld der DI und IoC gegeben zu haben. Und wie einfach und komfortabel man diese Pattern mit Symfony einsetzen kann.

Da ich DI und IoC auch jahrelang im Javaumfeld eingesetzt habe und es nun auch in PHP nun ausprobiert habe, kann ich sagen, dass es eine feine Sache ist. Grundsätzlich ist es ein wenig Mehraufwand. Die so oft hochgehaltene Flexibilität tritt in der Praxis aber eher nie ein. Weil bei einem Ansatz ohne DI kann man sich Stück für Stück seine Klasse aufbauen. Bei DI muss man sie konfigurieren. Bei jeder Änderung fasst man die Klasse als auch die Konfigurationsdatei an. Ebenso bekommt man damit einen Single-Point-of-Failure. Wenn die Konfigurationsdatei kaputt geht geht im Zweifel gar nix mehr. Ebenso arbeiten so alle Teammitglieder an der Konfigurationsdatei, was zu häufigeren Merges führen kann. Dies kann man jedoch durch aufteilen in mehrere Konfigurationsdateien und entsprechende Includes verringern. Bleibt noch der Aufwand, das man jedesmal zwei Dateien anfasst anstatt nur eine. Soweit zu den negativen Aspekten. Die Vorteile sind aber auch sehr deutlich. Jeder weiß wo er schauen muss wenn ein Logger oder eine Klasse konfiguriert werden soll. Kein Wühlen mehr durch viele Bootstrap und Konfig-Klassen oder gar durch die halbe Anwendung. Man hat einfach Ordnung. Man kann Anwendungsweit einen Logger schnell mal aktivieren oder deaktivieren. Man kann durch ein geschicktes Deployment konfigurativ auf einem Live-System einen Debugger deaktivieren ohne in der Entwicklung irgend etwas verändern zu müssen. In der Entwicklungsumgebung hat man also weiterhin seine Debugausgaben.

Das ganze ist also auf jeden Fall ein guter Schritt in Richtung saubere Architektur, weil es eine Menge kleiner Schweinereien die man so gerne macht unterbindet. Und aus diesem Grund gehört dieses Pattern ab einer gewissen Projektgröße eigentlich zu den Must-Haves.