/usr/portage

Dependency Injection Container Refactorings, Part Two 0

This is part of a mini-series about typical refactorings when using DI containers. Read part one.


(c) Jil A. Brown

Introduce Parameter

When configuring objects you will stumble upon occurrences of duplicated configuration. As configuration duplication is as bad as code duplication, making refactorings and maintenance time-intense and error-prone, we try to avoid them. Occurrences I had, started from defining the same hosts over and over for different services and quasi hard-coded upload prefixes for files sprinkled all over my configuration. I will illustrate this refactoring with the image upload example. We configure Zend_File_Transfer and add a few validators to allow image uploads but only specific ones:

<?xml version="1.0"?>
<container>
   <services>
      <service id="fileTransferService" class="Zend_File_Transfer">
          …
         <call method="addValidator">
            <argument>Count</argument>
            …
            <argument>photo</argument>
         </call>
         <call method="addValidator">
            <argument>Size</argument>
            …
            <argument>photo</argument>
         </call>
         <call method="addValidator">
            <argument>MimeType</argument>
            …
            <argument>photo</argument>
         </call>
         <call method="addValidator">
            <argument>ImageSize</argument>
            …
            <argument>photo</argument>
         </call>
      </service>
   <services>
</container>

When adding validators to Zend_File_Transfer the fourth argument (in this case photo) is the name of the array key of the file. In our case the markup would look like this:

<input type="file" name="photo"/>

The specific key is important if you allow the upload of various file types in one request. Now we change the requirements and allow not only photos but photos and PDFs (in the same input as photos, so that the user does not need to use different inputs based on file formats). To not mislead the next programmer working on this piece of code, we should change the markup to something like this (give me a better name please):

<input type="file" name="photoOrPdf"/>

Now we open our container configuration and change every occurrence of “photo” to “photoOrPdf” and hope not to forget one. Except the one you’ll find out two month later. To avoid this duplication of configuration, we introduce a parameter and our container configuration changes.

<?xml version="1.0"?>
<container>
   <parameters>
       <parameter key="filePrefix">photoOrPdf</parameter>
   </parameters>
   <services>
      <service id="fileTransferService" class="Zend_File_Transfer">
          …
          <call method="addValidator">
             <argument>Count</argument>
             …
             <argument>%filePrefix%</argument>
          </call>
          <call method="addValidator">
             <argument>Size</argument>
             …
             <argument>%filePrefix%</argument>
          </call>
          <call method="addValidator">
             <argument>MimeType</argument>
             …
             <argument>%filePrefix%</argument>
          </call>
          <call method="addValidator">
             <argument>ImageSize</argument>
             …
             <argument>%filePrefix%</argument>
          </call>
       </service>
   </services>
</container>

To make things even more smooth we could inject that parameter into the view and into the controller to make sure, configuration value duplication is no longer an issue with this specific module.

Parametererize Service

Excluded, as I no longer think this is actually a good idea.

Allow Environment Specific configuration

When you have a development process where you pass several acceptance stages before an artefact goes into production, these stages are typically slightly different from each other. Starting from different service IP addresses over single machine vs. multi machine, there will definitely be some variance among them. Typical variances are:

One way to do so is to sprinkle conditions all over your application and check on which host you are but that will lead to an application well beyond manageability. That’s why I was never happy (at least for large applications >100 person-days) with typical PHP application configurations like the preposterous config.inc.php. Having a touring complete programming language at hand for configuration will eventually introduce ugly conditionals making configurations unreadable. But I digress.

There are various models for stage configuration, including inheritance from each former stage, inheritance from a main configuration, standalone configuration and all mixes of these models. All of them are well implementable with the Symfony 2 dependency injection container. Let’s start with the most simplistic one, standalone configuration for each stage:

<?php
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(…));
$loader->import($currentStage . '.xml');

A more complicated one is main configuration + override per stage:

<?php
$container = new ContainerBuilder();
$baseLoader = new XmlFileLoader($container, new FileLocator(…));
$baseLoader->import('main.xml');
$stageLoader = new XmlFileLoader($container, new FileLocator(…));
$stageLoader->import($currentStage . '.xml');

The most “complicated” would be linear inheritance, where testing extends development, staging extends testing and so on:

<?php
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(…));
foreach (array('development', 'testing', 'staging', 'production') as $stage) {
    $loader->import($stage . '.xml');
    if ($stage == $currentStage) {
        break;
    }
}

With this kind of setup you can override configuration.

Example main.xml:

<?xml version="1.0"?>
<container>
   <parameters>
        <parameter key="database.name">application</parameter>
        …
   </parameter>
   <services>
       <service id="component" class="MyComponent"/>
       <service id="component2" class="MyComponent2"/>
   </services>
</container>

testing.xml with different database name and an alternative for component2:

<?xml version="1.0"?>
<container>
   <parameters>
        <parameter key="database.name">another_database</parameter>
   </parameter>
   <services>
      <service id="component2" class="MyAlternativeComponent2"/>
   </services>
</container>

Filed on 06-05-2011, 17:05 under , , , & no comments & no trackbacks

Dependency Injection Container Refactorings, Part One 15

This is part of a mini-series about typical refactorings when using DI containers. Read part two.


(c) Jil A. Brown

Working heavily with the Symfony2 Dependency Injection Container, I feel that we found some typical refactorings towards a DI container that emerge during the introduction of such a component. I want to write down the preliminary results of trying to systematize more or less as a draft. I will use the Symfony2 DI container configuration as an example but most of the refactorings should be applicable to other containers as well, some of them even to dependency injection without a container.

Make Dependency Explicit

This is typically the first step towards Dependency Injection: make a dependency explicit. There are three typical ways to do so, first is constructor injection, second is setter injection and third and less preferred is property injection. I roughly prefer constructor injection for invariant dependency in my domain and setter injection for infrastructure (setNotifier e.g.). Consider this example:

<?php
namespace Example;
class Client
{
    public function execute()
    {
        $dependency = new Dependency();
        $dependency->execute();
    }
}

Client creates a new instance of Dependency and call execute(). Bad for testing and for configuration, Dependency will always be hard coded there. To make it easier manageable we refactor towards setter injection:

<?php
namespace Example;
class Client
{
    public function setDependency(Dependency $dependency)
    {
        $this->_dependency = $dependency;
    }
 
    public function execute()
    {
        $this->_dependency->execute();
    }
}

Now we can manage Client in the DI container like this:

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </service>
    </services>
</container>

We see that the dependency is explicit: we specifically configure Example\Client and pass a specific Example\Dependency object.

Introduce Interface Injection

After a number of Explicit Dependency refactorings our configuration file for the service container will become huge. We will notice that we have common dependencies that are used at various places, an event manager for example. To fix that rapid growth we choose to utilize Interface Injection to ease configuration.

This is the configuration starting point:

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </service>
        <service name="example.anotherClient" class="Example\AnotherClient">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
            <call method="setOtherDependency">
                <argument type="service">
                    <service class="Example\OtherDependency"/>
                </argument>
            </call>
        </service>
    </services>
</container>

We notice that both Example\Client and Example\AnotherClient depend on Example\Dependency. First of all we need an interface contracting setDependency. This is basically the Extract Interface refactoring. We call the newly extracted interface Example\DependencyAware.

The interface:

<?php
namespace Example;
interface DependencyAware
{
    public function setDependency(Dependency $dependency);
}

And we refactor both Example\Client and Example\AnotherClient to implement Example\DependencyAware.

Now we change our configuration to call setDependency no longer explicitly for Example\Client and Example\AnotherClient but for every object implementing Example\DependencyAware.

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client"/>
        <service name="example.anotherClient" class="Example\AnotherClient">
            <call method="setOtherDependency">
                <argument type="service">
                    <service class="Example\OtherDependency"/>
                </argument>
            </call>
        </service>
    </services>
    <interfaces>
        <interface class="Example\DependencyAware">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </interface>
    </interfaces>
</container>

Expose Service

Really simple, but …
Expose Service is applied when a service has been only a dependency but should be used as a top level service. We start with the well known example:

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service">
                    <service class="Example\Dependency"/>
                </argument>
            </call>
        </service>
    </services>
</container>

Consider we want to expose Example\Dependency as a service directly, we need to change from the configuration above to
reference the service by ID.

<?xml version="1.0"?>
<container xmlns="http://www.symfony-project.org/schema/dic/services">
    <services>
        <service name="example.dependency" class="Example\Dependency"/>
        <service name="example.client" class="Example\Client">
            <call method="setDependency">
                <argument type="service" id="example.dependency"/>
            </call>
        </service>
    </services>
</container>

Simple.

Next topics would be: Introduce Parameter, Parametererize Service and Allow Environment Specific configuration

Filed on 19-04-2011, 21:09 under , , , & 15 comments & one trackback