Using a Dependency Injection Container with Zend_Application

Much has been written on Dependency Injection lately, mostly by Padraic Brady (1, 2) and Fabien Potencier (1, 2, 3, 4, 5). My subjective feeling tells me there are now more PHP DI containers out there than CMS or ORMs implemented in PHP, including two written by myself (an overengineered and a useful one). Its an awesome pattern if used on a larger scale and can (re-) wire a complex business application according to a clients needs without having to change much of the domain code. It aims at a complete seperation of object instantiation and dependency tracking from the business logic.

Beginning with version 1.8 Zend Framework is able to integrate any of these DI containers into its Zend_Application component easily. The Application component intializes a set of common resources and pushes them into the MVC stack as additional Front Controller parameters. Technically a Zend_Registry instance holds all these resources with their respective resource names as keys. The resources are accessible inside the Front Controller and the Action Controller classes. Assume the resource 'Db' is initialized in your application, you can access it with:

$front = Zend_Controller_Front::getInstance();
$container = $front->getParam('bootstrap')->getContainer();
$db = $container->db;
 
// or:
class FooController extends Zend_Controller_Action {
    public function barAction()
    {
        $container = $this->getInvokeArg('bootstrap')->getContainer();
        $db = $container->db;
    }
}

This is pretty much dependency injection already, but the default registry approach suffers from two serious problem: New instances can only be added to the Container by implementing new Zend_Application resources and these instances cannot be lazy loaded. All resources used by Zend_Application are always loaded on every request. But the application container implementation was developed with Dependency Injection in mind and is not tied to the use of Zend_Registry. Only three magic methods are required by any container that wants to be Zend_Application compliant: __get(), __set() and __isset(). Each instantiated resource is pushed via __set() into the container. If required by another resource, __isset() is used to check wheater a resource with the given name exists inside the container and __get() is used to retrieve the instances from the container.

Some month ago I extended Yadif, a lightweight PicoContainer-like DI container for PHP written by Thomas McKelvey, with several features that make it a very powerful DI container in my opinion. It has a detailed documentation and examples on the GitHub Page if you want to check it out. I extended Yadif to be Zend_Application compliant in a way that the application-wide Zend_Application resources can be used as dependencies for objects that are lazy-loaded from the container. Skipping the tech-talk, here is an example. First we have to replace the default Container with Yadif:

$objects = new Zend_Config_Xml(APPLICATION_PATH."/config/objects.xml");
$container = new Yadif_Container($objects);
 
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/config/application.xml'
);
// Set Yadif as Container
$application->getBootstrap()->setContainer($container);
$application->bootstrap()
            ->run();

Assume you run Zend_Application with a "Db" and a "Cache" resource. These resources are loaded on every request. The objects configured in Yadif however are not instantiated until they are requested for the first time from the container. We can merge these two worlds and make use of the Application resources inside the Yadif Configuration "objects.xml", which looks like:

<?xml version="1.0" ?>
<objects>
    <myModel>
        <class>MyModel</class>
        <arguments arg1="db" arg2="cache" />
    </myModel>
</objects>

Instantiating a myModel class inside the Action Controller uses the the Database and Cache resources as constructor arguments:

class FooController extends Zend_Controller_Action {
    public function barAction()
    {
        $container = $this->getInvokeArg('bootstrap')->getContainer();
        $myModel = $container->myModel;
    }
}

Given the possibilites by the Yadif_Container you are now empowered to use Dependency Injection for all your application objects and make use of Zend_Applications resource system. Furthermore any other dependency injection container, simple or complex, can also be integrated easily.

Share This Post

  • Share on Twitter
  • Facebook
  • Share on deli.cio.us
  • Share on Digg
  • Share on reddit
  • Share on StumbleUpon

Comments


avi block
Jun, 16. 2009

To make it really useful, there must be a way to use the container to get services dropped directly into the controllers. The only two ways I can think of doing this would be a custom dispatcher, or an action helper which uses reflection and setter injection.
Unfortunately creating a new dispatcher means duplicating a lot of code.



beberlei
Jun, 16. 2009

I use the init() method for this, its still very convenient :-)

But you could also extend ZEnd_Controller_Action and write a generic init() or constructor that checks via reflection which services should be loaded.



brenton alker
Jun, 17. 2009

Very nice. Just had a quick play and it seems like a great way to free my code from Factory-itis.

Had a minor issue though, it seems to check that the classes exists when you load the configuration. Which happens before you bootstrap the autoloader, so the classes aren't found and it throws an exception. I've forked it and removed that check and it seems fine. I don't know if there is another workaround for this I've missed?



tsmckelvey
Jun, 17. 2009

Ben -- love what you're doing with Yadif.



federico
Jun, 17. 2009

Simple and gets the job done, well done. The API of Yadif reminds me to the one I created last year, Zend_Di: http://bit.ly/14rLlW

Interesting post, thanks.



tsmckelvey
Jun, 17. 2009

Federico, that's because I was looking forward to Zend_Di but it didn't seem to be going anywhere. I liked the Zend_Di API, so credit should go to you for that inspiration.



thesorrow
Jun, 18. 2009

Good article.
what we are doing in our apps is to create an actionHelper that will test with the "property_exists" method if a field need to be set in the Controller :) and then i just need to declare public variable in my controller and initialize my action helper :)



federico
Jun, 18. 2009

I like it, It's not overengineered like most of the PHP DI Containers. Why don't you propose Yadif to ZF? Matthew mentioned he wanted to add something like that to Zend_Application, after Fabien added it to Symfony.



leslie
Jun, 20. 2009

I do not use Zend Framework for I fear it is seriously over weight... so is my sister but by the way, but on your average basic shared hosting package, this framework kills the server.

That is unfortunate as in the early days I actually had high hopes so will a DI container help? I'm not convinced; few people who actually know what they're doing will get great use out of it but for many it'll be "just another feature" that will be abused :(

Roll on the day when PHP has it's own DI container at the internal level...



simon
Jul, 29. 2009

I think DI is the solution to my problem of trying to decouple controllers from models when unit testing. One thing I'm unsure of though, if I request myModel twice e.g.

$myModel1 = $container-myModel;
$myModel2 = $container-myModel;

do I get the same instance of myModel back?



keith
Sep, 08. 2009

Hi,

I keep getting class not found warnings as the autoloader has not been initialized before the objects are added to the container, which checks for them, am I doing something wrong?



carstep
Aug, 05. 2010

Hi, is the DI in this form nothing more than a better approach for autoloading classes? Except for singletons.

r. Sandor


Write a Comment