/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

Trackbacks

Trackback specific URI for this entry

No Trackbacks

Comments

No comments

Add a Comment & let me know what you think