Monday, July 28, 2008

Dependency Injection in Zend Framework

See Dependency Injection in Zend Framework at its new home on bradley-holt.com.

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.

9 comments:

wllm said...

More details! Please create a proposal. :)
Also, take a look at Ralph's Zend_Reflection component for any reflection capability you may need. He's adding something for docblocks specifically.

Unknown said...

Great to see that someone is picking up DI again since the Zend_Di Proposal is archieved.
I'll definitly keep an eye on your proposal. :-)

Anonymous said...

Finally a good news. I'm glad Zend_Di has inspired you to write a similar component. Don't write a simple DI container, is useless, look at the bigger picture, modules (models, controllers). I'm glad your friend Matthew has finally authorised this. I'll be helping you with the proposal.

Ian said...

Interesting idea! I wonder if there isn't a way to use interfaces though, perhaps have a standard (blank) interface than users can extend, and only the setters in that interface on a given object are treated as dependency setters, in case people are using getters and setters for non-dependency properties, as is pretty common.

Could even have two sets, for required and available (though perhaps that would be a little awkward).

Anonymous said...

wllm - thanks for your interest! I've created a draft proposal and will post more details when it's ready. Yes, Zend_Reflection would be very helpful (especially DocBlock reflection).

mike - thanks! I'll post more when the draft proposal is ready.

phpimpact - sorry, I didn't mean to say that Zend_Di is useless. I just think it has too many features and is too complicated for Zend Framework (at least at this time). As you say, Zend_Di is, in fact, the inspiration for this proposal. Also, the proposal I'm writing is just a draft and has in no way been approved by anyone at Zend.

Ian Barber - good ideas. I would definitely like a way to declare required dependencies and perhaps even optional dependencies. I was thinking of trying to use DocBlock tags for this.

Anonymous said...

I agree, one of the goals is to replace Zend_Registry, so a simple container should do the work. In fact, that's the reason why I proposed Zend_Di in the first place, Zend_Registry didn't allow me to have local containers. I've been trying to push this idea forward for almost a year now. And I think it's great that you got involved, you know this guys, and you can make this happen. And that's what we want right?

Anonymous said...

phpimpact - I'm not completely convinced that Zend Framework users are ready for a dependency injection container. The very concept of dependency injection may be too complicated for Zend Framework which does a fabulous job of balancing simplicity and power. What I mean is that dependency injection may be too complicated for the power it provides. That's why I'm advocating for a container that is as simple as possible and has very clear and narrow use cases. IMHO, the simpler the container the more likely it is to be a good fit for Zend Framework.

Unknown said...

Very interesting proposal! I would like to recommend choosing a slightly less generic name though. Zend_Container can mean so many different things to different people, that I think a more specific name would be appropriate.

Anonymous said...

Ivo - thanks! I tried to think of a more specific name but couldn't come up with anything that I liked. I'd like to avoid putting DI or IOC in the name as that focuses on what the component does, not what the component is. Do you have any suggestions for a more specific name?