Deprecated Behaviour

The inane, sometimes insane, ramblings from the mind of Brenton Alker.

Dependency Injection Container Resource in Zend Framework

A good dependency injection container is a godsend when it comes to managing the dependency tree of even a moderately complex domain model. As such, it comes as no surprise there has been much discussion about them of late in the PHP and Zend Framework communities.

Based on the Yadif and Benjamin Eberlei’s recent look at Using a Dependency Injection Container with Zend_Application, where he replaces Zend_Applications default container instance (A Zend_Registry instance) with a Yadif_Container, I have created a Zend_Application_Resource to allow configuration based injection of dependencies into the container via the normal ZF configuration file (application.ini)

The container resource copies any already instantiated objects from the old container into the new one, then replaces the default container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
class Tek_Application_Resource_Container extends Zend_Application_Resource_ResourceAbstract
{
    protected $_container = null;

    public function getContainer()
    {
        if (null === $this->_container) {
            $options = $this->getOptions();
            $container = $this->_getBootstrap()->getContainer();
            if (!$container instanceof Yadif_Container) {
                $config = isset($options['options']) ? new Zend_Config($options['options']) : null;
                $container = new Yadif_Container(array(), $config);

                // import instances from the existing (Zend_Registry) container
                foreach ($this->_getBootstrap()->getContainer() as $key => $instance) {
                    $container->__set($key, $instance);
                }
            }
            $container->addComponents($options['objects']);
            $this->_container = $container;
        }

        return $this->_container;
    }

    public function init()
    {
        $this->getContainer();
        $this->_getBootstrap()->setContainer($this->_container);
        return $this->_container;
    }

    protected function _getBootstrap()
    {
        $app = $this->getBootstrap()->getApplication();
        if ($app instanceof Zend_Application) {
            return $app->getBootstrap();
        } else {
            return $app;
        }
    }
}

I’ve also created a simple action helper to allow easy grabbing of resources from the action controllers. Both reside in my extensions repository.

To use the container resource you will need to add the prefix and path to the bootstrappers plugin loader:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
// ...
    protected function _initPlugins()
    {
       $this->getPluginLoader()->addPrefixPath(
           'Tek_Application_Resource',
           'Tek/Application/Resource'
       );
    }
// ...
}

Then you can add the resources and their dependencies via the normal configuration system. This means adding lines like:

resources.container.objects.Log.class = "Zend_Log"
resources.container.objects.Log.arguments.0 = "Log_Writer"

resources.container.objects.Log_Writer.class = "Zend_Log_Writer_Stream"
resources.container.objects.Log_Writer.arguments.0 = "%Log_Writer.stream%"

resources.container.options.Log_Writer.stream = APPLICATION_PATH "/../log/application.log"

There are 2 resources defined here, the “Log” and the “Log_Writer”.

  1. Log is an instance of Zend_Log and takes a Log_Writer resource as the first (and only) argument to its constructor.

  2. The Log_Writer resource is an instance of Zend_Log_Writer_Stream and takes a scalar as its only argument. The scalar value is defined in the container option specified.

Now, the controller can write a log like this.

1
2
3
4
5
6
<?php
$logger = $this->getInvokeArg('bootstrap')->getResource('Log');
// or, with the helper
$logger = $this->_helper->resource('Log');

$logger->log('A log message', Zend_Log::NOTICE);

While this is a simple example, it can be really beneficial when working with, for example, a service layer. The service you need might depend on another service, both of which may depend on an Authorization service. All the services depend on their data mappers (which themselves depends on a database connection) and their entity factories, etc. Instantiating a dependency tree like this for every object you need can lead to duplicate and hard to modify code. Dependency injection coupled with a good container can provide highly versatile code whose behaviour can be drastically changed by only modifying a configuration file.