Ibuildings has an article on Dependency Injection and Zend Framework Controllers. In the comments, the Zend_Di proposal is mentioned. I had posted several comments when the Zend_Di component was proposed. Most of my comments were ideas on how to make a dead-simple dependency injection container for Zend Framework. Zend Framework prides itself on simplicity and, in my humble opinion, if Zend Framework is going to have a dependency injection component this component needs to be as simple as possible – something along the lines of PicoContainer. So, I’d like to summarize my thoughts here. I should probably just write a proposal, but I’d like to start here and see what kind of feedback I get.
For the purposes of this blog entry, I’ll call this component Zend_Container (I apologize if this name has been proposed for any other components). First, some basic assumptions:
- The component’s primary purpose is to replace the use of class-managed singletons and Zend_Registry.
- The component will only manage singleton items within a container, it will not act as a factory (except for creating the single instance).
- A container can have zero or one parent container and have access to items in its parent, but a parent cannot have access to items in its children.
- The component uses reflection to determine dependencies.
Below is an example of a class that will have its dependent items injected:
class Zoo {
/**
* @var Feline
*/
protected $_feline;
/**
* @var Canine
*/
protected $_canine;
/**
* Sets the Feline for the Zoo to have.
*
* @param Feline $feline
* @return void
*/
public function setFeline(Feline $feline) {
$this->_feline = $feline;
}
/**
* Sets the Canine for the Zoo to have.
*
* @param Canine $canine
* @return void
*/
public function setCanine(Canine $canine) {
$this->_canine = $canine;
}
}
Here is an example of wiring up dependencies:
$container = new Zend_Container();
//note the ability to specify class or interface and class
$container->addComponent('Zoo')
->addComponent('Feline', 'Tiger')
->addComponent('Canine', 'Wolf');
$zoo = $container->getComponent('Zoo');
//the above is equivalent to (assuming setter injection)
$feline = new Tiger();
$canine = new Wolf();
$zoo = new Zoo();
$zoo->setFeline($feline);
$zoo->setCanine($canine);
An example of parent/child relationships:
$rootContainer = new Zend_Container();
//passing in the root container so the child is aware of its parent
$childContainer = new Zend_Container($rootContainer);
//note again that we can add either class or class and interface
$rootContainer->addComponent('SomeClass');
$childContainer->addComponent('SomeClass');
$instanceA = $rootContainer->getComponent('SomeClass');
/*
Child containers should have access to their parent's components (but not the other
way round) so if we hadn't added the component specifically to the child container
this next line would have given us instanceA.
*/
$instanceB = $childContainer->getComponent('SomeClass');
echo (int) ($instanceA === $instanceB); // echoes "0" (false)
There is one major problem that I have not figured out. Reflection is used to determine potential dependencies. The container then would look to see if it (or its parent) contains the dependency and if it does, wire it up, otherwise ignore it. In other words, there’s no way to assert that a component is a required dependency. Perhaps this could be solved through the use of an @required DocBlock tag if this can be read using PHP’s reflection mechanism. There could also be a configuration option that would simply make your addComponent calls for you.