Added by Federico Cargnelutti, last edited by Matthew Weier O'Phinney on Jul 01, 2008  (view change)

Labels

 
(None)

Zend Framework: Zend_Di Component Proposal

Proposed Component Name Zend_Di
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Di
Proposers Federico Cargnelutti
Matthew Weier O'Phinney, Zend Liaison
Revision 0.1 - 21 November 2007: Initial Proposal. (wiki revision: 67)

Table of Contents

1. Overview

Zend_Di is a dependency injector component. It minimizes coupling between groups of classes, makes unit testing much simpler, and provides an easy way to re-configure a package to use custom implementations of components. The architecture of the Zend_Di component is based on the following concepts:

  • Dependency injection is a technique that consists of passing objects via the constructor or setter methods.
  • The Container provides an easy way of re-configuring a package to use custom implementations of components.
  • Responsibility for object management is taken over by whatever container is being used to manage those objects.

Benefits of using a DI Container:

  • Easy best practice unit testing
  • Component reuse
  • Centralized configuration
  • Clean and declarative architecture
  • Maintainability and adaptability

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • This component will use Reflection to wire dependencies.
  • This component will use Configuration to wire dependencies.
    • This component must support PHP, XML and INI configuration files.
  • This component must allow all components to be constructed using Zend_Config instances.

4. Dependencies on Other Framework Components

  • Zend_Config (optional)
  • Zend_Exception
  • Zend_Loader

5. Theory of Operation

Zend_Di provides generic factory classes that instantiate instances of classes. These instances are then configured by the container, allowing construction logic to be reused on a broader level. For example:

Once we separate configuration from use, we can easily test the Car with different Engines. It's just a matter of re-configuring the package and injecting Zend_Car_Parts_Engine_Gas instead of Zend_Car_Parts_Engine_Fuel.

6. Milestones / Tasks

  • Milestone 1: [DONE] Design interface
  • Milestone 2: [DONE] Write proposal
  • Milestone 3: [DONE] Gather feedback and revise design as necessary
  • Milestone 4: Review by the Zend team
  • Milestone 5: Develop full implementation and unit tests
  • Milestone 6: Documentation
  • Milestone 7: Future enhancements

7. Class Index

  • Zend_Di
  • Zend_Di_Container
  • Zend_Di_Factory
  • Zend_Di_Reflection
  • Zend_Di_Parameter
  • Zend_Di_Data
  • Zend_Di_Registry
  • Zend_Di_Storage_Interface
  • Zend_Di_Storage_Object
  • Zend_Di_Storage_Exception
  • Zend_Di_Exception

8. Use Cases

Zend_Di handles injections via the constructor or setters methods. In addition, the component allows the user to map out specifications for components and their dependencies in a configuration file and generate the objects based on that specification.

Assembling Objects Using Reflection

UC-01

Assembling objects using Zend_Di_Reflection

UC-02

Assembling objects using Zend_Di_Container

Assembling Objects Using Configuration

The configuration is typically set up in a different file. Each package can have its own configuration file: PHP, INI or XML file. The configuration file holds the components specifications and package dependencies.

You can pass an instance of Zend_Config via the constructor, or set a configuration array using the setConfigArray() method.

The cases below assume that the following classes have been defined:

UC-01

The two major flavors of Dependency Injection are Setter Injection (injection via setter methods) and Constructor Injection (injection via constructor arguments). Zend_Di provides support for both, and even allows you to mix the two when configuring the one object.

Constructor dependency injection

When a class is loaded, the constructor method is selected by default.

UC-02
UC-03
UC-04

Users can map out specifications for components and their dependencies. So whenever a class is loaded, Zend_Di will inject the dependencies automatically. For example:

UC-05

Setter dependency injection

UC-06

Zend_Di injects dependencies using the top-down fashion, starting with the constructor and ending with the setter methods.

UC-07

Users can map out specifications for a component:

UC-08

Storage Containers

You can tell Zend_Di what components to manage by adding them to a container (the order of registration has no significance). Containers are stored are retrieved using the Zend_Di_Registry class. The Zend_Di_Registry::getContainer() method returns an instance of Zend_Di_Storage_Interface.

UC-09

You can register your own container as long as you pass an instance of Zend_Di_Storage_Interface. New containers can be register using the Zend_Di_Registry::setStorage() method.

UC-10

Real-life example

Please visit the following page:
http://framework.zend.com/wiki/display/ZFPROP/Zend_Di+Example

9. Class Skeletons

  • Zend_Di_Container
  • Zend_Di_Factory
  • Zend_Di_Parameter
  • Zend_Di_Registry
  • Zend_Di_Storage_Object

I like this proposal! This would be a very useful extension for the ZF and other Components of the ZF.

A "real-life" Use Case would be a nice addition to this proposal.

Hi Thomas,

Yes, I agree. I've replaced the Zend_Automobile example with a real example.

Thanks for the feedback.

Hi!

Thank you! That was fast! Very good example.

Another example is the current front controller. As it is a singleton, it is hard coded in a lot of helper which receive the request object via the front controller. Extending the front controller it is pretty ugly, as you need to change the front controller to a pseudo-singleton (where Zend_Controller_Front::getInstance() and CustomFrontController::getInstance() refers to the same instance). With dependency injection this would be fixed.

A more general comment: I really like your proposal as I'm a big fan of the dependency injection pattern (see my proposal for Zend_Config_Configurable which is also related). The issue is, that it would require large refactoring efforts as basically every component needs to be changed to use dependency injection to make it makes sense. But I would vote nevertheless for doing it. One concern I have is that from my experience dependency injection is one of the hardest pattern to get which could decrease the adoption rate of the Zend Framework because users feel overstrained with complexity.

Hi Lars, I know exactly what you mean and the idea is not to change other components in the ZF, but to offer developers an additional component that they can use to build objects and test them.

What you are saying about complexity and users looking for simpler options is true. And that's what I'm after, the most difficult thing, simplicity. I'll be adding some of Padraic's new ideas, and hopefully we'll end up having a more simple and versatile component.

I also consider Zend_Config_Configurable very important and I hope it gets added to the ZF.

0.3 Revision

  • Removed Zend_Registry dependency. No need to have a global registry.
  • Added Zend_Di_Container_Storage_Interface, allows users to register custom containers.
  • The addDependency() method name was changed to addComponent().
  • Naming convention: Zend_DI has been renamed to Zend_Di (my mistake, sorry).
  • Instance of Zend_Config is now passed via the constructor.
  • Added the setConfigArray() method. Zend_Config is optional.

Quick comment before I review fully. Could I suggest adding an ultra simple method for instantiating objects using a shorter API? Mainly it's to keep folk not making full use of the DI happy about having a shorter means of allowing for object substitution. The suggestion posted to the mailing list involved:

// In a test/spec

Zend_Di::replaceClassWithObject('Zend_Mail', new ZMail_Mocked);

// In actual controller

Zend_Di::instance('Zend_Mail');

Unknown macro: {/code}

Switched around the interface to play on Zend_Di. The reason for the static methods is simply because this would be a fire and forget operation without configuration files or any in-depth requirement for DI beyond allow object substitution for tests.

On a finer point, this package in general is a great idea but you're complicating my life already . I now have to ensure PHPSpec supports both the short-form method suggested, as well as the full breadth of Zend_Di's capabilities for its Zend Framework Context. I'll try to comment further after a better review.

Is there actual working source code for the package yet? Not pressing too hard - if it's still in progress fair play. Look forward to Zend_Di's inclusion soon.

Hi Paddy,

I like the idea and I'll implement it this weekend. I can see the benefits of adding those static methods to Zend_Di, like you said, it will allow users to replace objects without the need of having config files, and that can be very handy.

I'm planning to rename Zend_Di_Component_Factory to Zend_Di_Factory, remove Zend_Di_Component_*, and create Zend_Di.,

Is there actual working source code for the package yet?

Yes, I wrote it almost 2 months ago and I've been using it since.

Thanks for your feedback and suggestions

Also, Zend_Di_Container_Storage acts as a local registry container that allows users to store objects, also providing an interface in case they want to make it a global registry (extending Zend_Registry and implementing Zend_Di_Container_Storage_Interface, for example.

Marcus Boerger wrote a similar class for the SPL library, called SplObjectStorage, and it's very similar to Zend_Di_Container_Storage. The problem is that this class will be available since php 6. The good thing is that making the switch in the future will not be a problem.

SplObjectStorage is available in 5.2.5 and in 5.3.

Interesting, thanks. What's the minimum requirement for the ZF?

Hi Federico,

If you have source code you could spare for a review, I'd really love to get hold of it (under license and with an NDA promise until you're ready). This is my #1 most wanted component right now .

Paddy

Np, let me make those changes this weekend and I'll send you the code by email. You are more than welcome to change it, improve it or do what ever you like with it.

I've updated the code, you can see the changes here: SVN

I've added 2 static methods to Zend_Di, create() and replaceClass():

I have the source and am reviewing now, Federico . Looks good - I'll feed back any comments I have in the next day or two.

My first take is that this is rather complicated and could be simplified quite a bit. I would suggest a light weight approach that doesn't try to solve every use case (at least not yet). The biggest thing I'd recommend would be to not use configuration as the primary means of wiring dependencies. Why not simply pass the class name to addComponent (or interface and class name) and use reflection to determine dependencies? Certainly configuration files could be used, optionally, to replace calls to addComponent. Here's an example of a simplified use case:

The one problem I see with the example I just provided is that it assumes type hinted parameters might be dependencies. This would have to be thought through a little more to consider all the potential pitfalls. I understand that using a config solves this problem, but a I fear it's an overly burdensome requirement. If PHP supported meta data that could be used in reflection (e.g. reading an @dependency tag in the DocBlock) that would also solve the problem.

The newInstance() method implies these are not primarily managed as singletons. My understanding is that DI containers are typically used to manage singletons, can you explain your intent here a little more? If these are, in fact, singletons then maybe getComponent() (like in PicoContainer) instead of newInstance()? The ability to pass in values and bind parameters seems like an advanced use case, I wouldn't focus on that as much. As I said a second ago, my understanding is that the primary use of DI would be to replace singletons and registries. For the most part, these should be stateless.

An earlier comment talked about the possibility of a DI container being used as the primary means of getting at objects throughout the framework (the example was in MVC). I think this is worth considering. If this were used as the main "registry" (for lack of a better word) for Zend Framework components, child containers would be a nice feature. There would be a root container and one container for each module (if you use MVC modules). It looks like you might be doing something like this with the container manager. But, if containers are, themselves, meant for managing objects whey can't this be simplified to a parent/child relationship? In other words, Zend_Di_Container_Manager seems redundant since Zend_Di_Container's job is to manage things.

One final note on one of your examples: I'm not sure if extending the container for a specific use case is something you'd want to encourage.

Just to clarify one point - Dependency Injection gained popularity primarily out of two immediate needs in applications:

1. How to inject a changeable network of objects into another object, without in-code declaration (i.e. it needed to be externally configured) of the instantiation, configuration and injection of other objects.

2. How to substitute objects being directly instantiated within other objects when applying Test-Driven Development where the object or system under test (SUT) must be isolated from all it's immediate dependencies.

It is not intended to prevent the use of Singletons and Registries, though I will readily agree that in the Zend Framework that is probably a very valid concern. The truth is that in applying TDD to a Zend Framework controller the very last thing you ever want to rely on is Zend_Registry's static methods. Why? Because you can't mock or stub a static class easily. Or rather you can, but it bumps up the complexity of your tests because now all tests must manage yet another global value set.

I suppose another interesting use case of DI is to replace a design pattern called the Abstract Factory. It's more interesting in that you do see a lot of literature from Java these days talking about using a complex solution to a simple Abstract Factory problem. Sometimes the extra weight does come in handy though, and since a DI container already exists, it's less code to just use it.

I would suggest that a Zend Framework DI container not also be an Abstract Factory. In my opinion, it should solve one problem and solve it well. The biggest need I see, and you touched on this, is for a replacement to Zend_Registry's static methods (at first for user objects and then perhaps Zend Framework components). I would suggest, for simplicity, that a Zend Framework DI container only focus on managing singletons. For this reason, I suggested renaming newInstance() to getComponent(). If a use case requires more than one instance of an object, it can be created in a separate instance of the container, perhaps with parent/child relationships between containers. For example:

Certainly an Abstract Factory (or equivalent) would be a nice addition to Zend Framework but I think it should be a separate component, perhaps sharing some common pieces with the DI component. DI can be a complicated subject for developers to understand. I'd think we would want to make it easy for people to use it correctly without too much extra stuff that they could shoot themselves in the foot with.

To clarify (is that a favourite word of mine? ), I didn't mean to suggest DI replace an AF entirely, or even in the majority of use cases for an AF, but in some select cases it works as well as a separate Abstract Factory. The literature is already full of enough warnings of DI overtaking AF that total substitution of one for the other has already been found questionable.

With regard to configuration vs compositional building - where do I substitute in Mock Objects in your above use case? As it stands with Zend_Di I have two options depending on whether the static methods were used (simple redirection through Zend_Loader::loadClass() with ReflectionClass::newInstanceArgs() at the end) or a configuration file (just statically substitute a new configuration generated by the test).

Zend_Di needs to keep it's eye on two balls here: testing facility, and object wiring...

By wiring up objects as class and interface:

instead of just class:

and then having your dependency be on the interface:

not on the implementation:

then we can simply wire up a mock Feline:

In the case that your mocks are generated by a testing facility then addComponent() should support being passed an object instance instead of a class name:

We get both testing and object wiring this way

Thanks for the feedback

> [Bradley] My first take is that this is rather complicated and could be simplified quite a bit. I would suggest a light weight approach that doesn't try to solve every use case (at least not yet).

That's basically what I thought when developing the component. There are 2 well known approaches and trying to cover all the test cases could result in a big and complex component. So I focused on the wiring approach for 2 reasons: I consider dependency mapping though configuration to be more flexible and powerful, and the other reason is that this approach integrates better with a container based persistence system that I'm developing.

I'm aware that are people that prefer configuration and others don't, IMO DI containers should be flexible and adaptable. So, my question is, should ZF provide a component that offers both approaches and covers all the use cases? Should I refactor this component to handle both approaches?

I agree with Padraic when he says that the Zend_Di it needs to keep it's eye on testing facility and object wiring. And I also agree with what you are saying, it would be great if we could offer the user both approaches, and by looking at your example, it seems possible and shouldn't be difficult to implement.

About Zend_Di_Container_Manager, I think you are right, it should be called Zend_Di_Storage_Manager, because it manages the Storage containers and not the Container itself. Storage and Manager and both part of Zend_Di_Container.

I think this component can be improved by all of us, but first it needs to by approved by Zend. DI containers are becoming more and more popular every day in the php world, and I hope it gets approved, because like Wil said: "ZF is set by the opinions of the ZF community right now". And right now, everyone is getting familiar with DI containers and some developers are even building their own. In the last 3 months, 3 new PHP DI containers have been developed, one of them is an excellent port of the latest version of PicoContainer.

I think a DI container could be a great addition to Zend Framework, thank you for proposing it

I think you should support setting up components through a configuration file, just not necessarily dependencies. IMHO, dependencies should be handled solely through reflection so that I can quickly add new dependencies in my code without having to update a configuration file (I am aware that there are potential pitfalls to this approach that would have to be explored). I see the configuration option as being a shortcut to typing a bunch of calls to addComponent() - it would basically make all the calls to addComponent() for me. For example:

would essentially do this for me:

At the very least, let me do everything in code that I could do with a configuration file. There are currently no components (that I am aware of) in Zend Framework that require me to use configuration. I like the ability to go in and get my hands dirty if I want.

I'm not intimately familiar with the innards of every DI container, but perhaps Bradley can suggest how to implement an automated Reflection system? I think non-configuration would be possible - in a sense allow for configuration to drive the manual API via a Factory solution (which likely exists in implementation as is, so refactor, refactor ).

The problem I have with Reflection is that it's unaware of how the component will be used, and so can't really predict future behaviour. It's usefullness may well be limited to constructors, and even then only to required parameters. I think you underestimate the complexity of that - I have enough interesting moments tracking Mocked dependency classes in PHPMock where I need to obey the real class's API requirements and type hinting by using Reflection in a similar fashion.

I suppose what I'm digging at is whether automated injection using Reflection is capable of Setter Injection - where Setters can be both required or optional. How will Reflection help make that decision without user interference?

I'm with Federico on one thing - we need Zend to approve this component pronto . I'd suggest emailing Wil after 1.5 is released and see if he'll slot into the review process quickly. Until Wil implements some of the review process changes he's hinted at the review process will remain a quagmire of extreme slowness except for Zend sponsored components. And I so really want to see Zend_Di in the Incubator yesterday...

Here is a quick way to determine potential dependencies. This is obviously hacked together and could be more elegantly packaged:

Like I said before, there are potential pitfalls to this approach that would have to be explored. My current thinking is that code similar to what is above would be used to determine potential dependencies when getComponent() is called. For each potential dependency that is actually a registered component with the container, the container calls getComponent() on that. This process repeats, recursively, until all dependencies have been resolved. The immediately obvious pitfall is that you intended your component to have a dependency but, if the component you depend on hasn't actually been added to that container then the container silently skips it instead of throwing an exception. In other words, with my current proposal, there is no way to declare that you have a required dependency.

Pádraic: after re-reading your comment I realize I didn't directly address your issue about setters being required or optional. What I am proposing, and this certainly needs to be thought out more, is that any parameter of any public function (perhaps limited to constructors and functions beginning with "set") that has a type is considered a potential dependency and is confirmed as a dependency if its type is found in the container. Assuming the container only manages singletons, this may not be as bad as it sounds at first read . Why would you ever have a parameter's type be a class that was intended to be a singleton and not expect to get a reference to the one instance of that singleton (unless you didn't realize the class was meant to be a singleton in which case you already have a logic error anyways)? I will have to explore Zend_Server_Reflection a little more and see if there is a way it can be used to discover the intent of the original class author (i.e. is it a dependency at all and, if so, is it required or optional).

Setters can be both required or optional. How will Reflection help make that decision without user interference?

I agree. That's one of the issues of using code, user intervention is needed. You can't expect the Reflection system to decide for you.

What you are suggesting Bradley is to inject singletons to all the setter methods that have a type hint followed by a non-optional parameter (ReflectionParameter::isOptional()===false), right? But how does the Reflection system know which setters are required (assuming that some of them are optional) and what to inject (new or single instances)?

What I'm proposing does not detect whether a dependency is required or optional. That is probably the biggest drawback to what I am suggesting but I still think the simplicity it provides may be worth the trade.

Everything in my proposal is based on the assumption that the container is only meant to handle singletons and that it cannot be used as a factory of any sort (other than, of course, creating the singleton the first time it is needed). If the container can also be used as a factory, then what I have suggested does not work. So, part of my proposal is to simplify the container to focus on replacing registries and the use of class-managed singletons as I see that being the most likely use case.

My proposal would not use ReflectionParameter::isOptional() for anything as it would be pointless, let me explain why. Every parameter (that has a type) to every public method would be considered a potential dependency and then be confirmed as a dependency if its type in fact registered in the container. For example, given the following class:

When the Zoo class is added to the container, it would detect Feline, Canine, and Visitor as potential dependencies. However, in this example, only Feline and Canine are singletons and managed by the container. When the Zoo class is first instantiated by the container it would confirm Feline and Canine as dependencies, since they are managed by the container, and silently (and correctly in this case) not call addVisitor() since there is no Visitor managed by the container. As you can see in this example, just because ReflectionParameter::isOptional() would give us false for the $visitor parameter of addVisitor() does not mean that Visitor is a required dependency, or more correctly, a dependency at all.

The obvious problem here is in the case that there were no Canine managed by the container then we have no way of telling the container that Canine was a required dependency and an exception would not be thrown. One could argue, though, that we would certainly get an exception the first time we tried to use the null $_canine property and that may be good enough. Whether or not the simplicity that this provides is worth the trade-offs is certainly up for debate.

View the rest of this thread. Most recent comment: Feb 10, 2008
5 more comments by: Bradley Holt, Federico Cargnelutti

Revision 0.4: API reworked

Classes:

Bradley, your proposal looks interesting, I also think it will be a nice thing to have, and I already have some ideas on how we can implement them once it moves to the incubator and we have access to the repository. I have reworked the API, and I recommend future changes to take place after the proposal gets reviewed by the dev-team.

Bradley (and anyone else), feel free to edit this proposal and add your own ideas to it. Like you said, you might be able to simplify or explain better the use cases. Remember, the nature of a wiki, a svn repository and a community itself, is team work. We've seen some cases of "my component" or "my proposal", but this is not the case, it goes against my XP principles. So go on, feel free to use the wiki

Assembling Objects Using Reflection

If this was to be added, we need to think about what to do if we have more than one object that implements the same interface. For example:

I would suggest that the interface (or class) that the component is registered under acts as the key and there can only be one key in a container. A component may be registered under its own type or any of its parent types (interfaces or classes) but its registered type is what needs to be unique. If you try to register a component under an interface or class that it does not implement or extend, you would get an exception. If you try to register a component under and interface or class that has already been registered in the container, you would get an exception.

In your example above (applying my suggestion), you would have two components registered: Zend_Foo_Component_A and Zend_Foo_Component_Interface. The single instance of Zend_Foo_Component_Interface (which happens to be a Zend_Foo_Component_B) is what would be injected in both parameters of the constructor (which would obviously be pointless) and in the setter. In other words, a class with dependency would have to ask for the exact type it wants and it would only get injected if that exact type has been registered.

Note that with my suggestion two components of the same actual type could be registered in a container but they would have to be registered under different interface or class types that they implemented or extended. Also, since the key is the most important part I would suggest reversing the parameters of addComponent():

Yes, that's a possible solution. But keep in mind that if you do it that way, you are sacrificing classes that inject multiple objects with a same interface.

What if the method accepts an optional argument called $class, which is the name of the dependency class, for example:

So, ReflectionParameter::getName() checks for the name, if the argument name is "class" it checks to see if a default value is available and if it is, it gets the component from the container and injects it.

Pros: Zend_Di supports injecting objects that implement the same interface.
Cons: It still needs user intervention in order to work.

I'd like a DI container that only manages a single object registered under a given type. I'd prefer that my DI container not also act as a factory of any sort (other than first creating the object instance, of course). This approach seems much simpler to work with and is consistent with other DI containers I've worked with. Of course, this is just my personal preference

Unfortunately the solution above requires the dependent class to both have knowledge of the specific object type (which defeats the purpose of programming to an interface) and to (effectively) have knowledge of the container's API. Components should be container agnostic for the most part - they should be POPOs (Plain Old PHP Objects). The one exception to this would be the use of meta data (if PHP reflection supported this, e.g. a DocBlock @dependency tag).

Note that I am suggesting it be possible for two objects of the same concrete type to be registered, just under different keys. So, the following would work to get both instances of the same concrete object registered under different types:

View the rest of this thread. Most recent comment: Feb 16, 2008
6 more comments by: Bradley Holt, Federico Cargnelutti

Hey guys,

This is a great proposal, I would like to register my vote for this

Also whilst looking around trying to find out more about DI in general I stumbled across some other PHP5 implementations that you may be interested in:

http://xyster.devweblog.org/about/di - Xyster ZF extension
http://www.seasar.org/en/php5/DIContainer.html - PHP5 seasar port

Not sure if these solve any of the issues you guys have been talking about, but I thought they might be worth knowing about.

From looking at the PHP reflection docs it does look possible to get metadata from methods, not too sure about properties atm.

Thx

Keith

Hi Keith,

PicoContainer has been ported to PHP in various occasions, Pawel ported it in 2005 and now Xyster ported the latest version of PicoContainer. The idea of this proposal, Zend_Di, is to create a simple and flexible DI container without porting the complexity of Java. Basically, any component or library can be ported, but I thought it would be interesting to create one based on the different ideas that are floating around in the php community. The way you inject a dependency using Zend_Di_Container is almost as easy as creating a form with Zend_Form. But if you decide to use Zend_Di_Reflection, it's even easier.

Cheers,
Fedrico.

This proposal was created 7 or 8 months ago, I'm moving it to the "Ready for review" section as it is becoming obsolete. What begin as a lightweight dependency injection container for PHP slowly turned into a more complex and unusable component, it's trying to do too much. If this component ever moves forward, I'll be more than happy to refactor it and keep it as simple as possible.

Zend Comment

We are archiving this proposal at this time. We agree with Federico that the proposal has become complex and unwieldy in its current format, and has few use cases within the framework itself (though it could have a number of use cases in userland code).

We encourage the original author and/or any other interested parties to revive the proposal by posting a new proposal in the future with a new implementation showing clean and compelling use cases.