Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

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: 68)

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

]]></ac:plain-text-body></ac:macro>

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Nov 28, 2007

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

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

    1. Nov 28, 2007

      fc

      <p>Hi Thomas, </p>

      <p>Yes, I agree. I've replaced the Zend_Automobile example with a real example.</p>

      <p>Thanks for the feedback. </p>

      1. Nov 29, 2007

        <p>Hi!</p>

        <p>Thank you! That was fast! Very good example.</p>

        1. Jan 17, 2008

          <p>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.</p>

          <p>A more general comment: I really like your proposal as I'm a big fan of the dependency injection pattern (see my proposal for <ac:link><ri:page ri:content-title="Zend_Config_Configurable" /><ac:link-body>Zend_Config_Configurable</ac:link-body></ac:link> 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.</p>

          1. Jan 29, 2008

            fc

            <p>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.</p>

            <p>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.</p>

            <p>I also consider Zend_Config_Configurable very important and I hope it gets added to the ZF.</p>

  2. Nov 30, 2007

    fc

    <p><strong>0.3 Revision</strong></p>

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

  3. Jan 18, 2008

    <ac:macro ac:name="unmigrated-wiki-markup"><ac:plain-text-body><![CDATA[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.]]></ac:plain-text-body></ac:macro>

    1. Jan 28, 2008

      fc

      <p>Hi Paddy,</p>

      <p>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.</p>

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

      <blockquote>
      <p>Is there actual working source code for the package yet?</p></blockquote>

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

      <p>Thanks for your feedback and suggestions <ac:emoticon ac:name="smile" /></p>

  4. Jan 29, 2008

    fc

    <p>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.</p>

    <p>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.</p>

    1. Mar 03, 2008

      <p>SplObjectStorage is available in 5.2.5 and in 5.3.</p>

      1. Mar 04, 2008

        fc

        <p>Interesting, thanks. What's the minimum requirement for the ZF?</p>

  5. Jan 30, 2008

    <p>Hi Federico,</p>

    <p>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 <ac:emoticon ac:name="wink" />.</p>

    <p>Paddy</p>

    1. Jan 30, 2008

      fc

      <p>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.</p>

      1. Feb 02, 2008

        fc

        <p>I've updated the code, you can see the changes here: <a href="http://svn.phpimpact.com/browser/trunk/library">SVN</a></p>

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

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        $foo = Zend_Di::create('Zend_Foo');

        $constructorArgs = array(new FooA(), new FooB(), 3, 4);
        $setterArgs = array('setComponentA' => array(new FooA(), 2));
        $mock = Zend_Di::create('Zend_Mock', $constructorArgs, $setterArgs);

        Zend_Di::replaceClass('Zend_Foo', $mock);
        ]]></ac:plain-text-body></ac:macro>

  6. Feb 03, 2008

    <p>I have the source and am reviewing now, Federico <ac:emoticon ac:name="smile" />. Looks good - I'll feed back any comments I have in the next day or two.</p>

  7. Feb 05, 2008

    <p>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:</p>

    <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
    //note the missing config parameter
    $container = new Zend_Di_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();
    //assume the parameter for setFeline is type hinted to Feline
    $zoo->setFeline($feline);
    //assume the parameter for setCanine is type hinted to Canine
    $zoo->setCanine($canine);
    ]]></ac:plain-text-body></ac:macro>

    <p>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.</p>

    <p>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.</p>

    <p>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.</p>

    <p>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.</p>

  8. Feb 06, 2008

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

    <p>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.</p>

    <p>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.</p>

    <p>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.</p>

    <p>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.</p>

    1. Feb 06, 2008

      <p>I would suggest that a Zend Framework DI container <em>not</em> 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 <em>only</em> 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:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      //again, suggesting that we not use configs, only reflection
      $rootContainer = new Zend_Di_Container();
      //passing in the root container so the child is aware of its parent
      $childContainer = new Zend_Di_Container($rootContainer);
      //suggesting here 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)
      ]]></ac:plain-text-body></ac:macro>

      <p>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.</p>

  9. Feb 06, 2008

    <p>To clarify (is that a favourite word of mine? <ac:emoticon ac:name="wink" />), 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.</p>

    <p>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).</p>

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

    1. Feb 06, 2008

      <p>By wiring up objects as class and interface:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      //testable
      $container->addComponent('Feline', 'Tiger');
      ]]></ac:plain-text-body></ac:macro>

      <p>instead of just class:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      //not testable but still "works" (Tiger is registered as type Tiger)
      $container->addComponent('Tiger');
      ]]></ac:plain-text-body></ac:macro>

      <p>and then having your dependency be on the interface:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      //testable
      class Zoo
      {
      /**

      • @var Feline
        */
        protected $_feline;

      public setFeline(Feline $feline)

      Unknown macro: { $this->_feline = $feline; }

      }
      ]]></ac:plain-text-body></ac:macro>

      <p>not on the implementation:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      //not testable
      class Zoo
      {
      /**

      • @var Tiger
        */
        protected $_tiger;

      public setTiger(Tiger $tiger)

      Unknown macro: { $this->_tiger = $tiger; }

      }
      ]]></ac:plain-text-body></ac:macro>

      <p>then we can simply wire up a mock Feline:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      $container->addComponent('Feline', 'MockFeline');
      ]]></ac:plain-text-body></ac:macro>

      <p>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:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      //assumes $mockFeline has already been created by the testing facility
      $container->addComponent('Feline', $mockFeline);
      ]]></ac:plain-text-body></ac:macro>

      <p>We get both testing and object wiring this way <ac:emoticon ac:name="smile" /></p>

  10. Feb 07, 2008

    fc

    <p>Thanks for the feedback <ac:emoticon ac:name="smile" /></p>

    <p>> <ac:link><ri:page ri:content-title="Bradley" /></ac:link> 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).</p>

    <p>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. </p>

    <p>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?</p>

    <p>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. </p>

    <p>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.</p>

    <p>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 <strong>right now</strong>". 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. </p>

    1. Feb 07, 2008

      <p>I think a DI container could be a great addition to Zend Framework, thank you for proposing it <ac:emoticon ac:name="smile" /></p>

      <p>I think you <em>should</em> 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:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      $components = array(
      array(
      'component' => 'Zoo',
      ),
      array(
      'component' => 'Feline',
      'implementation' => 'Tiger',
      ),
      array(
      'component' => 'Canine',
      'implementation' => 'Wolf',
      ),
      );
      $container = new Zend_Di_Container($components);
      ]]></ac:plain-text-body></ac:macro>

      <p>would essentially do this for me:</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      $container = new Zend_Di_Container();
      $container->addComponent('Zoo')
      ->addComponent('Feline', 'Tiger')
      ->addComponent('Canine', 'Wolf');
      ]]></ac:plain-text-body></ac:macro>

      <p>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 <em>require</em> me to use configuration. I like the ability to go in and get my hands dirty if I want.</p>

  11. Feb 08, 2008

    <p>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 <ac:emoticon ac:name="wink" />).</p>

    <p>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.</p>

    <p>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?</p>

    <p>I'm with Federico on one thing - we need Zend to approve this component pronto <ac:emoticon ac:name="wink" />. 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...</p>

    1. Feb 08, 2008

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

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      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)
        Unknown macro: { $this->_feline = $feline; }

      /**

      • Sets the Canine for the Zoo to have.
        *
      • @param Canine $canine
      • @return void
        */
        public function setCanine(Canine $canine)
        Unknown macro: { $this->_canine = $canine; }

      }

      $dependencies = array();
      $class = Zend_Server_Reflection::reflectClass('Zoo');
      $methods = $class->getMethods();
      foreach ($methods as $method) {
      $prototypes = $method->getPrototypes();
      foreach ($prototypes as $prototype) {
      $parameters = $prototype->getParameters();
      foreach ($parameters as $parameter)

      Unknown macro: { $dependencies[] = $parameter->getType(); }

      }
      }
      /*
      Array
      (
      [0] => Feline
      [1] => Canine
      )
      */
      print_r($dependencies);
      ]]></ac:plain-text-body></ac:macro>

      <p>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 <em>required</em> dependency.</p>

    2. Feb 08, 2008

      <p>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 <em>only</em> manages singletons, this may not be as bad as it sounds at first read <ac:emoticon ac:name="smile" />. 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).</p>

      1. Feb 08, 2008

        fc

        <blockquote><p>Setters can be both required or optional. How will Reflection help make that decision without user interference?</p></blockquote>

        <p>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. </p>

        <p>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)? </p>

        1. Feb 09, 2008

          <p>What I'm proposing does <em>not</em> 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 <em>may</em> be worth the trade.</p>

          <p>Everything in my proposal is based on the assumption that the container is <em>only</em> 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.</p>

          <p>My proposal would <em>not</em> 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 <em>potential</em> dependency and then be confirmed as a dependency if its type in fact registered in the container. For example, given the following class:</p>

          <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
          class Zoo {

          /**

          • @var Feline
            */
            protected $_feline;

          /**

          • @var Canine
            */
            protected $_canine;

          /**

          • @var array
            */
            protected $_visitors;

          /**

          • Sets the Feline for the Zoo to have.
            *
          • @param Feline $feline
          • @return void
            */
            public function setFeline(Feline $feline)
            Unknown macro: { $this->_feline = $feline; }

          /**

          • Sets the Canine for the Zoo to have.
            *
          • @param Canine $canine
          • @return void
            */
            public function setCanine(Canine $canine)
            Unknown macro: { $this->_canine = $canine; }

          /**

          • Adds a visitor to the Zoo.
            *
          • @param Visitor $visitor
          • @return void
            */
            public function addVisitor(Visitor $visitor)
            Unknown macro: { $this->_visitors[] = $visitor; }

          }
          ]]></ac:plain-text-body></ac:macro>

          <p>When the Zoo class is added to the container, it would detect Feline, Canine, and Visitor as <em>potential</em> 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 <em>not</em> mean that Visitor is a required dependency, or more correctly, a dependency at all.</p>

          <p>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.</p>

          1. Feb 09, 2008

            fc

            <p>I have an idea. There are 2 different approaches for wiring objects, using reflection or configuration. The component needs to be redesigned in order to support both. I would need to change it to something like this:</p>

            <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
            Zend_Di_Configuration()
            Zend_Di_Reflection()
            ]]></ac:plain-text-body></ac:macro>

            <p>The first one wires an object using configuration, the second one using reflection. They can both use the same container class, Zend_Di_Container. The manager class, Zend_Di_Container_Manager, no longer exists, Zend_Di_Container now manages all the components. </p>

            <p>I think that, considering how different both approaches are, this will give us more freedom to develop an assembler that uses only Reflection.</p>

            <p>So, should we separate both approaches first? What do you think?</p>

          2. Feb 10, 2008

            fc

            <p>I think that for the benefit of this proposal we should split it in 2. Otherwise, if we try to cover everything from the start, the proposal might never move forward. We know what the requirements are, and based on that I can redesign the component with extensibility in mind. This will allow other developers to extend it by creating their own proposal based on a different DI approach.</p>

            1. Feb 10, 2008

              <p>In reply to your first comment, it may be possible to simply have a configurable option - something along the lines of "assumeDependencies". If set to true, it will assume everything is a potential dependency. If set to false, it would only wire up dependencies that were specifically configured in the container (either using the container's API or by passing a config).</p>

              <p>In reply to your second comment, are you suggesting an alternative (perhaps even complementary) proposal with a simplified set of use cases? I would be happy to draft this proposal if this is what you are suggesting. This might be good to see where each proposal ends up (let each have a life of its own since they are trying to solve different use cases), and then eventually combine the best parts of each.</p>

              <p>I know that you would like to see a DI container approved for Zend Framework ASAP. My guess is that since DI containers can be very complicated for developers to understand that Zend will not rush the inclusion of such a container. In other words, they will probably expect a lot more dialog about how a DI container would be used by both Zend Framework users and component developers. My ideas about how such a container would be used could certainly use a lot more scrutiny <ac:emoticon ac:name="smile" /></p>

              1. Feb 10, 2008

                fc

                <p>The idea is to create proposals and improve existing ones, so if you think you can improve this proposal or extend it, just go for it <ac:emoticon ac:name="smile" /> The reflection approach has already been developed, so it shouldn't be difficult to implement something similar, Phemto is a good example of this. </p>

                <blockquote>
                <p>DI containers can be very complicated for developers to understand</p></blockquote>

                <p>Well, this particular component is being used in my company and everyone found it very usable and straight forward. And considering that some developers had never used a DI container before, it proves that this particular API works. </p>

                <p>I redesigned the API based on user comments and it's almost done. Why don't you play around with it and see if you can implement your ideas based on the existent code, and if you can't, then yes, you should create a new proposal explaining your ideas. That would definitely benefit the framework, its users and this proposal itself.</p>

                1. Feb 10, 2008

                  <p>I see the link to browse the repository using Trac but do you have the svn repository itself available so I can easily check out a working copy?</p>

  12. Feb 10, 2008

    fc

    <p><strong>Revision 0.4: API reworked</strong></p>

    <p>Classes:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    Zend_Di_Container
    Zend_Di_Factory
    Zend_Di_Registry
    Zend_Di_Parameter
    Zend_Di_Exception
    Zend_Di_Storage_Interface
    Zend_Di_Storage_Object
    Zend_Di_Storage_Exception
    Zend_Di_Data_Type
    ]]></ac:plain-text-body></ac:macro>

  13. Feb 11, 2008

    fc

    <p>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. </p>

    <p>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 <ac:emoticon ac:name="smile" /></p>

  14. Feb 12, 2008

    fc

    <p><strong>Assembling Objects Using Reflection</strong></p>

    <p>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:</p>

    <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
    class Zend_Foo {
    public function __construct(
    Zend_Foo_Component_Interface $componentA = null,
    Zend_Foo_Component_Interface $componentB = null,
    $arg3 = null,
    $arg4 = null) {
    }

    public function setComponentA(Zend_Foo_Component_Interface $component, $arg2 = null) {
    }
    }

    // Example of more than one object implementing the same interface
    $di = new Zend_Di_Reflection();
    $di->addComponent(new Zend_Foo_Component_A());
    $di->addComponent('Zend_Foo_Component_B', 'Zend_Foo_Component_Interface');
    ]]></ac:plain-text-body></ac:macro>

    1. Feb 13, 2008

      <p>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 <em>registered</em> 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.</p>

      <p>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 <em>exact</em> type it wants and it would only get injected if that exact type has been registered.</p>

      <p>Note that with my suggestion two components of the same <em>actual</em> 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():</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      $di = new Zend_Di_Reflection();
      //only the key is provided (in this case the specific object type)
      $di->addComponent(new Zend_Foo_Component_A());
      //key and specific object type are provided
      $di->addComponent('Zend_Foo_Component_Interface', 'Zend_Foo_Component_B');
      ]]></ac:plain-text-body></ac:macro>

      1. Feb 13, 2008

        fc

        <p>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.</p>

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

        <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
        class Zend_Foo {
        ...

        public function setComponentA(Zend_Foo_Component_Interface $component, $class = 'Zend_Foo_Component_A') {
        }
        }
        ]]></ac:plain-text-body></ac:macro>

        <p>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.</p>

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

        1. Feb 13, 2008

          <p>I'd like a DI container that <em>only</em> manages a single object registered under a given type. I'd prefer that my DI container <em>not</em> 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 <ac:emoticon ac:name="smile" /></p>

          <p>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).</p>

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

          <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
          class Zend_Foo {
          ...

          public function setComponent(Zend_Foo_Component_Interface $component) {
          }

          public function setComponentA(Zend_Foo_Component_A $componentA) {
          }
          }

          $di = new Zend_Di_Reflection();
          //Zend_Foo_Component_A registered as Zend_Foo_Component_A
          $di->addComponent(new Zend_Foo_Component_A());
          //Zend_Foo_Component_A registered as Zend_Foo_Component_Interface
          $di->addComponent('Zend_Foo_Component_Interface', 'Zend_Foo_Component_A');
          ]]></ac:plain-text-body></ac:macro>

          1. Feb 13, 2008

            fc

            <p>Sorry my mistake, it's nonsense what I wrote above. Yes, I'm definitely with you in this one. What about adding a 2nd and optional parameter to the addComponent() method:</p>

            <p><strong>addComponent</strong> ( mixed component <ac:link><ri:page ri:content-title=", array method" /></ac:link>] )</p>

            <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
            class Zend_Foo {
            ...
            public function __construct(Zend_Foo_Component_A $componentA) {}
            public function setComponentA(Zend_Foo_Component_Interface $component) {}
            public function setComponentB(Zend_Foo_Component_Interface $component) {}
            public function setComponentC(Zend_Foo_Component_C $component) {}
            }

            $di = new Zend_Di_Reflection();
            // Class Zend_Foo_Component_A implements Zend_Foo_Component_Interface
            $di->addComponent(array('Zend_Foo_Component_A'=>'Zend_Foo_Component_Interface'), array('__construct', 'setComponentA'));
            // Class Zend_Foo_Component_B implements Zend_Foo_Component_Interface
            $di->addComponent(new Zend_Foo_Component_B());
            // Class Zend_Foo_Component_C
            $di->addComponent(new Zend_Foo_Component_C());
            ]]></ac:plain-text-body></ac:macro>

            <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
            Zend_Di_Reflection::$_spec
            Array
            (
            [Zend_Foo_Component_A] => Array
            (
            [class] => Zend_Foo_Component_A
            [instanceOf] => Zend_Foo_Component_Interface
            [method] => Array
            (
            [0] => __construct
            [1] => setComponentA
            )
            )

            [Zend_Foo_Component_B] => Array
            (
            [class] => Zend_Foo_Component_B
            [instanceOf] => Zend_Foo_Component_Interface
            [method] => Array
            (
            [0] => setComponentB
            )
            )
            [Zend_Foo_Component_C] => Array
            (
            [class] => Zend_Foo_Component_C
            )
            )

            Zend_Di_Reflection::$_typeHints
            Array
            (
            [Zend_Foo_Component_Interface] => Array
            (
            [0] => Zend_Foo_Component_A
            [0] => Zend_Foo_Component_B
            }
            }
            ]]></ac:plain-text-body></ac:macro>

            1. Feb 14, 2008

              <p>This definitely works but it still requires a certain amount of "configuration" above and beyond simply throwing classes (or objects) into the container. <em>If</em> you're going to allow more than one component registered under the same type I certainly can't think of a better approach unless we can read the DocBlock annotations through reflection. Sebastian Bergmann talks about that here:</p>

              <p><a class="external-link" href="http://sebastian-bergmann.de/archives/488-Annotations-in-PHP.html">http://sebastian-bergmann.de/archives/488-Annotations-in-PHP.html</a></p>

              <p>If this works, then we could simply annotate the class itself <em>or</em> use configuration (whichever the user prefers):</p>

              <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
              class Zend_Foo {
              ...
              /**

              • The inject annotation here is not needed since we aren't
              • changing the type to inject, but it doesn't break anything.
                *
              • @param Zend_Foo_Component_A $componentA
              • @inject Zend_Foo_Component_A
                */
                public function __construct(Zend_Foo_Component_A $componentA) {}

              /**

              • Here we are saying we would prefer to have the more specific
              • Zend_Foo_Component_A injected. I'm not sure why we wouldn't
              • just register Zend_Foo_Component_A as Zend_Foo_Component_A
              • instead of having both Zend_Foo_Component_A and
              • Zend_Foo_Component_B registered as Zend_Foo_Component_Interface
              • and then use Zend_Foo_Component_A as the parameter type thus
              • negating the need for the inject annotation.
                *
              • @param Zend_Foo_Component_Interface $component
              • @inject Zend_Foo_Component_A
                */
                public function setComponentA(Zend_Foo_Component_Interface $component) {}

              /**

              • See the comment above for setComponentA.
                *
              • @param Zend_Foo_Component_Interface $component
              • @inject Zend_Foo_Component_B
                */
                public function setComponentB(Zend_Foo_Component_Interface $component) {}

              /**

              • Note that this would still work without the inject annotation.
                *
              • @param Zend_Foo_Component_C $component
                */
                public function setComponentC(Zend_Foo_Component_C $component) {}
                }
                ]]></ac:plain-text-body></ac:macro>

              <p>This is a little messy since technically the annotations apply to the entire function, not the specific parameter. However, I <em>think</em> that reflection actually uses the type of the param annotation <em>not</em> they type that is type hinted in the function. If this is true, you could ditch the inject annotation all-together and simply "lie" in the param annotation. A little off-topic, but this could potentially address your earlier question about required or optional dependencies but I won't go into that yet as I haven't fully thought it through.</p>

              1. Feb 15, 2008

                fc

                <p>Yes, Jeff Moore is doing something similar with his DI container for the Wact framework, but IMO the DI container has to adapt to the API and not the other way around. By using the DocBlock, you are assuming that all the users can edit the file and add comments (@inject or @param). And in most of the cases, only core developers have this privilege. </p>

              2. Feb 16, 2008

                fc

                <p>If you think about it, a file with annotations is a non standard configuration file, because it doesn't follow any standards. If you use annotations as configuration parameters you are converting that file in a configuration file. And I don't see a lot of advantages of using annotations as configuration parameters, over xml, ini or yaml for example. Also, I think that comment blocks should first become a standard in order for us to use them as config params.</p>

                <p>But still, when it comes to DI containers, people are talking about this stuff, and I'm sure everyone has its own opinion about it <ac:emoticon ac:name="smile" /></p>

                1. Feb 16, 2008

                  <p>I completely agree with you on this. I'd like to see a solution that works without <em>any</em> configuration at all. However, if configuration <em>is</em> required I think it's acceptable to allow the configuration to happen in either on the container level (by passing in configuration parameters) or at the class level via annotations. This would be especially useful when prototyping since you wouldn't have to be constantly updating your configuration (I agree configuration is cleaner in this scenario, just not always as practical). Whether or not annotations are considered a "standard" way of exposing information about a class in PHP is definitely a good point and I don't know the answer to that.</p>

                  <p>The bottom line is that configuration of some form or another is required if you don't enforce singleton (by registered type) rule within a container. This is probably the more fundamental issue where I have difference of an opinion as to how a DI container would be useful to PHP applications <ac:emoticon ac:name="smile" /></p>

          2. Feb 13, 2008

            fc

            <p>Zend_Di_Reflection::$_spec and Zend_Di_Reflection::$_typeHints arrays contain all the information I need to inject the dependencies.</p>

  15. Feb 23, 2008

    <p>Hey guys,</p>

    <p>This is a great proposal, I would like to register my vote for this <ac:emoticon ac:name="smile" /></p>

    <p>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:</p>

    <p><a class="external-link" href="http://xyster.devweblog.org/about/di">http://xyster.devweblog.org/about/di</a> - Xyster ZF extension
    <a class="external-link" href="http://www.seasar.org/en/php5/DIContainer.html">http://www.seasar.org/en/php5/DIContainer.html</a> - PHP5 seasar port</p>

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

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

    <p>Thx</p>

    <p>Keith</p>

  16. Feb 28, 2008

    fc

    <p>Hi Keith, </p>

    <p>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.</p>

    <p>Cheers,<br />
    Fedrico.</p>

  17. Jun 21, 2008

    fc

    <p>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.</p>

  18. Jul 01, 2008

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comment</ac:parameter><ac:rich-text-body>
    <p>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).</p>

    <p>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.</p></ac:rich-text-body></ac:macro>