[cvs] [Wiki] changed: Doc/Dev/Injector

Chuck Hagenbuch chuck at horde.org
Fri Jan 15 16:45:08 UTC 2010


chuck  Fri, 15 Jan 2010 11:45:08 -0500

Modified page: http://wiki.horde.org/Doc/Dev/Injector
New Revision:  1.2
Change log:  first pass on wiki markup

@@ -4,228 +4,227 @@

  Horde_Injector is a lightweight dependency-injection container.


-++ FAQ
+++ Dependency Injection Container FAQ

-h1. Dependency Injection Container FAQ
++++ Where can {{Horde_Injector}} be used?

-h2. Where can {{Horde_Injector}} be used?
+In the application layer only. If you use this in your business  
models I will find you and beat you to death with a shoe.

-In the application layer only. If you use this in your business  
models I will find you and beat you to death with a shoe.
++++ How do I provide {{Horde_Config}} values to my business models?

-h2. How do I provide {{Horde_Config}} values to my business models?
+Factories.

-Factories.
+<code type="php" title="Using factories to provide configuration  
variables to business models">
+$injector->bindFactory('InterfaceX', 'FactoryX', 'create');

-{code:title=Using factories to provide configuration variables to  
business models}
-$injector->bindFactory('InterfaceX', 'FactoryX', 'create');
+class FactoryX {
+    public function create(Horde_Injector $injector) {
+        $setting =  
$injector->getInstance('Horde_Config')->get('main', 'setting');
+        return new X($setting);
+    }
+}
+</code>

-class FactoryX {
-public function create(Horde_Injector $injector) {
-$setting = $injector->getInstance('Horde_Config')->get('main', 'setting');
-return new X($setting);
-}
-}
-{code}
++++ I have an {{array}}-typed parameter in my constructor, do I have  
to use a factory to provide the array of values?

-h2. I have an {{array}}-typed parameter in my constructor, do I have  
to use a factory to provide the array of values?
+Maybe. It depends on which you believe is easier. Consider this  
example where a more specific ArrayObject is used. Does this array get  
reused? If so it may be worth creating a special extension of  
ArrayObject.

-Maybe. It depends on which you believe is easier. Consider this  
example where a more specific ArrayObject is used. Does this array get  
reused? If so it may be worth creating a special extension of  
ArrayObject.
-{code}
-$injector->bindFactory('Dictionary_Sources', 'Dictionary_Sources_Factory');
-$dictionary = $injector->createInstance('Dictionary');
+<code type="php">
+$injector->bindFactory('Dictionary_Sources', 'Dictionary_Sources_Factory');
+$dictionary = $injector->createInstance('Dictionary');

-class Dictionary {
-public function __construct(Dictionary_Sources $sources) {
-...
-}
-}
+class Dictionary {
+    public function __construct(Dictionary_Sources $sources) {
+        ...
+    }
+}

-class Dictionary_Sources extends ArrayObject{}
+class Dictionary_Sources extends ArrayObject{}

-class Dictionary_Sources_Factory {
-public function create(Horde_Injector $injector) {
-return new Dictionary_Sources(array(
-$injector->getInstance('Dictionary_Source_Cache'),
-$injector->getInstance('Dictionary_Source_Db'),
-$injector->getInstance('Dictionary_Source_Json')
-));
-}
-}
-{code}
+class Dictionary_Sources_Factory {
+    public function create(Horde_Injector $injector) {
+        return new Dictionary_Sources(array(
+            $injector->getInstance('Dictionary_Source_Cache'),
+            $injector->getInstance('Dictionary_Source_Db'),
+            $injector->getInstance('Dictionary_Source_Json')
+        ));
+    }
+}
+</code>

  You're probably thinking that you could just create a factory to  
build your Dictionary object since you need to write a factory  
anyways. The real benefit is when you decide that Dictionary needs a  
new collaborator, say a {{Logger}}. If you have defined a factory for  
your {{Dictionary}} object, then you must edit that factory and create  
a {{Logger}} and pass it to your {{Dictionary}}. With the method  
above, you would simply need to edit your constructor, and the  
{{Logger}} will be provided for you. This gives you much greater  
flexibility, especially if you have objects that can operate on the  
same array of objects, but need slightly different configuration.



-++ Specification
+++ Dependency Injection Container Spec

-h1. Dependency Injection Container Spec
+----

-----
++++ Terms

-h2. Terms
+* Dependency Injection - DI
+* Dependency Injection Container - DIC

-* Dependency Injection - DI
-* Dependency Injection Container - DIC
+----

-----
++++ Requirements

-h2. Requirements
+Switching business layer implementations across an application is expensive.

-Switching business layer implementations across an application is expensive.
+DI decouples our business layer classes from other business layer  
classes but doesn't solve the problem of decoupling our application  
layer classes from our business layer classes. Without functionality  
to tackle this issue, business layer class configuration is duplicated  
throughout the application.

-DI decouples our business layer classes from other business layer  
classes but doesn't solve the problem of decoupling our application  
layer classes from our business layer classes. Without functionality  
to tackle this issue, business layer class configuration is duplicated  
throughout the application.
+As expense is the primary concern, any solution to the problem must  
not simply move the expense to initial application layer implementation.

-As expense is the primary concern, any solution to the problem must  
not simply move the expense to initial application layer implementation.
+----

-----
++++ Functional Spec

-h2. Functional Spec
-
-h3. Goals
+++++ Goals

  * Decouple application layer (controller) from business layer (model)
-* Allow easy use of models which are decoupled from their  
dependencies using DI
-* Favor code over configuration
-* Simple, testable design
+* Allow easy use of models which are decoupled from their  
dependencies using DI
+* Favor code over configuration
+* Simple, testable design
+
+++++ Features
+
+A DIC allows you to make typically difficult configuration changes,  
like changing an entire application from using one service  
implementation to another, with simple modifications in one localized  
place.

-h3. Features
+A DIC manages the way your objects are wired together. It makes the  
wiring entirely configurable per-application, per-module, and all the  
way down to per single object instantiation. Needing configuration to  
make lots of individual objects is inefficient and hard to maintain  
which is why its reserved for only special cases. In reality most  
classes rely on only a few service objects (configuration, database  
connection, cache, etc.) and these object most often will have  
identical configurations in the entire application with a few  
per-module exceptions.

-A DIC allows you to make typically difficult configuration changes,  
like changing an entire application from using one service  
implementation to another, with simple modifications in one localized  
place.
+DICs don't require configuration for every object you want them to  
create. In most cases class dependencies will be detectable and the  
DIC will be able to provide those dependencies automatically.

-A DIC manages the way your objects are wired together. It makes the  
wiring entirely configurable per-application, per-module, and all the  
way down to per single object instantiation. Needing configuration to  
make lots of individual objects is inefficient and hard to maintain  
which is why its reserved for only special cases. In reality most  
classes rely on only a few service objects (configuration, database  
connection, cache, etc.) and these object most often will have  
identical configurations in the entire application with a few  
per-module exceptions.
+Using a DIC for all application-level object creation makes your  
application unit-testable. Dependencies can easily be swapped out with  
mock objects because all dependencies are provided to the application  
using a DIC.

-DICs don't require configuration for every object you want them to  
create. In most cases class dependencies will be detectable and the  
DIC will be able to provide those dependencies automatically.
+----

-Using a DIC for all application-level object creation makes your  
application unit-testable. Dependencies can easily be swapped out with  
mock objects because all dependencies are provided to the application  
using a DIC.
++++ Technical Spec

-----
+The name of our DIC implementation is {{Horde_Injector}}.  
{{Horde_Injector}} can be told how to create objects using factories  
or it can try to create them itself using reflection. It can also be  
told to return an already-instantiated object when an interface is  
requested. Finally, if the requested interface has not been bound to a  
factory, implementation, or instance the injector will attempt to  
create the object using an implementation binder. For this to be  
successful the interface must actually be a concrete class.

-h2. Technical Spec
++++ Binders

-The name of our DIC implementation is {{Horde_Injector}}.  
{{Horde_Injector}} can be told how to create objects using factories  
or it can try to create them itself using reflection. It can also be  
told to return an already-instantiated object when an interface is  
requested. Finally, if the requested interface has not been bound to a  
factory, implementation, or instance the injector will attempt to  
create the object using an implementation binder. For this to be  
successful the interface must actually be a concrete class.
+The way we tell {{Horde_Injector}} how to create objects is using  
"binders" (which implement {{Horde_Injector_Binder}}).  
{{Horde_Injector}} maintains references to binders. References can be  
added in two ways:

-h3. Binders
+<code type="php" title="Using addBinder()">
+$binder = new Horde_Injector_Binder_X($param1);
+$injector->addBinder($interface, $binder)
+</code>

-The way we tell {{Horde_Injector}} how to create objects is using  
"binders" (which implement {{Horde_Injector_Binder}}).  
{{Horde_Injector}} maintains references to binders. References can be  
added in two ways:
+<code type="php" title="Using the magic method">
+$injector->bindX($interface, $param1);
+</code>

-{code:title=Using addBinder()}
-$binder = new Horde_Injector_Binder_X($param1);
-$injector->addBinder($interface, $binder)
-{code}
++++++ Factory binders

-{code:title=Using the magic method}
-$injector->bindX($interface, $param1);
-{code}
+[Factories|http://en.wikipedia.org/wiki/Factory_method_pattern] are  
classes with methods which instantiate and return objects. In the  
following example an interface is bound to a factory. If  
{{DataSourceX}} had dependencies it could instantiate them itself or  
ask {{$injector}} for those dependencies.

-h4. Factory binders
+<code type="php" title="Binding to a factory">
+$injector->bindFactory('DataSourceInteface', 'DataSourceFactory', 'create');

-[Factories|http://en.wikipedia.org/wiki/Factory_method_pattern] are  
classes with methods which instantiate and return objects. In the  
following example an interface is bound to a factory. If  
{{DataSourceX}} had dependencies it could instantiate them itself or  
ask {{$injector}} for those dependencies.
+class DataSourceFactory {
+    public function create(Horde_Injector $injector) {
+        return new DataSourceX();
+    }
+}
+</code>

-{code:title=Binding to a factory}
-$injector->bindFactory('DataSourceInteface', 'DataSourceFactory', 'create');
+{warning}Factory method names can be whatever you want but they must  
only require one parameter and it must be a {{Horde_Injector}}  
object.{warning}

-class DataSourceFactory {
-public function create(Horde_Injector $injector) {
-return new DataSourceX();
-}
-}
-{code}
++++++ Implementation binders

-{warning}Factory method names can be whatever you want but they must  
only require one parameter and it must be a {{Horde_Injector}}  
object.{warning}
+Reflection allows us to programmatically inspect the structure of the  
class that is to be instantiated. By reading the interface types of  
the class constructor's parameters and then asking the injector to  
create those objects as well we try to provide the requested class's  
constructor with all its dependencies.

-h4. Implementation binders
+<code type="php" title="Binding to an implementation">
+$injector->bindImplementation('DataSourceInteface', 'DataSourceX');

-Reflection allows us to programmatically inspect the structure of the  
class that is to be instantiated. By reading the interface types of  
the class constructor's parameters and then asking the injector to  
create those objects as well we try to provide the requested class's  
constructor with all its dependencies.
+class DataSourceX {
+    public function __construct(DependencyY $dependencyY) {
+        ...
+    }
+}
+</code>

-{code:title=Binding to an implementation}
-$injector->bindImplementation('DataSourceInteface', 'DataSourceX');
+Implementation binders also allow the calling of optional setter  
injection methods. Providing the method parameters here is done the  
same way as its done in the constructor, using reflection.

-class DataSourceX {
-public function __construct(DependencyY $dependencyY) {
-...
-}
-}
-{code}
+<code type="php" title="Setter injection example">
+$injector->bindImplementation('DataSourceInteface', 'DataSourceX')
+         ->bindSetter('setLogger');

-Implementation binders also allow the calling of optional setter  
injection methods. Providing the method parameters here is done the  
same way as its done in the constructor, using reflection.
+class DataSourceX {
+    public function __construct(DependencyY $dependencyY) {
+        ...
+    }

-{code:title=Setter injection example}
-$injector->bindImplementation('DataSourceInteface', 'DataSourceX')
-->bindSetter('setLogger');
+    public function setLogger(Logger $logger) {
+        ...
+    }
+}
+</code>

-class DataSourceX {
-public function __construct(DependencyY $dependencyY) {
-...
-}
-public function setLogger(Logger $logger) {
-...
-}
-}
-{code}
++++++ Choosing a binder

-h4. Choosing a binder
+Use a factory binder if:

-Use a factory binder if:
+* The class you are instantiating has any untyped parameters
+* You wish to create an instance of a class, that needs to have 2  
objects of the same interface, but configured differently. [See  
FAQ|Dependency Injection Container FAQ]

-* The class you are instantiating has any untyped parameters
-* You wish to create an instance of a class, that needs to have 2  
objects of the same interface, but configured differently. [See  
FAQ|Dependency Injection Container FAQ]
+Use an implementation binder if:

-Use an implementation binder if:
+* The class you are instantiating has only typed parameters

-* The class you are instantiating has only typed parameters
+++++ Instances

-h3. Instances
+{{Horde_Injector}} maintains an array of object instances which are  
bound to interfaces. Instance binding happens two different ways:  
setting the instance binding manually, or by asking the injector to  
give you an instance (if it doesn't have one, then one is created and  
a reference to the internal instances array is added.)

-{{Horde_Injector}} maintains an array of object instances which are  
bound to interfaces. Instance binding happens two different ways:  
setting the instance binding manually, or by asking the injector to  
give you an instance (if it doesn't have one, then one is created and  
a reference to the internal instances array is added.)
+<code type="php" title="Manually binding an instance to an interface">
+$instance = new X();
+$injector->setInstance('X', $instance);
+</code>

-{code:title=Manually binding an instance to an interface}
-$instance = new X();
-$injector->setInstance('X', $instance);
-{code}
+++++ Getting objects

-h3. Getting objects
+For requested objects to be returned the injector must already have  
all the information present it needs to create the object.

-For requested objects to be returned the injector must already have  
all the information present it needs to create the object.
++++++ Creating a new instance

-h4. Creating a new instance
+To get a guaranteed new object use createInstance. References to  
instances retrieved in this manner are not stored. They will not be  
available to other objects unless you use {{setInstance}} to store the  
instance on the Injector.

-To get a guaranteed new object use createInstance. References to  
instances retrieved in this manner are not stored. They will not be  
available to other objects unless you use {{setInstance}} to store the  
instance on the Injector.
+<code type="php" title="Creating an instance">
+$injector->createInstance('X');
+</code>

-{code:title=Creating an instance}
-$injector->createInstance('X');
-{code}
+{note}Although the return object will be new, its dependencies may  
not be. The injector will search its internal instances array for an  
instance matching the dependency's interface and if a match if found  
it will be used. If for some reason you need to guarantee that all  
dependencies are new, then you should consider using a factory  
binder.{note}

-{note}Although the return object will be new, its dependencies may  
not be. The injector will search its internal instances array for an  
instance matching the dependency's interface and if a match if found  
it will be used. If for some reason you need to guarantee that all  
dependencies are new, then you should consider using a factory  
binder.{note}
++++++ Getting an instance

-h4. Getting an instance
+As previously mentioned instances are pooled by the injector, so  
getInstance() gives developers the opportunity to reuse objects. If an  
instance exists for the requested interface it will be returned,  
otherwise it will be created, added to the injectors internal  
instances array, and returned.

-As previously mentioned instances are pooled by the injector, so  
getInstance() gives developers the opportunity to reuse objects. If an  
instance exists for the requested interface it will be returned,  
otherwise it will be created, added to the injectors internal  
instances array, and returned.
+<code type="php" title="Getting an instance">
+$injector->getInstance('X');
+</code>

-{code:title=Getting an instance}
-$injector->getInstance('X');
-{code}
+++++ Scoping with child injectors

-h3. Scoping with child injectors
+{{Horde_Injector}} implements the [Chain of  
Responsibility|http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern]  
design pattern with bindings and instances. What this means is that  
injectors will try to give you a binder or instance but if it doesn't  
have it it will ask its parent injector for them and try returning  
that to you.

-{{Horde_Injector}} implements the [Chain of  
Responsibility|http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern]  
design pattern with bindings and instances. What this means is that  
injectors will try to give you a binder or instance but if it doesn't  
have it it will ask its parent injector for them and try returning  
that to you.
+<code type="php" title="Child using a parent's binders">
+$injector->bindFactory('InterfaceX', 'FactoryX', 'create');
+$childInjector = $injector->createChildInjector();
+$x = $childInjector->createInstance('InterfaceX'); // success!!!
+</code>

-{code:title=Child using a parent's binders}
-$injector->bindFactory('InterfaceX', 'FactoryX', 'create');
-$childInjector = $injector->createChildInjector();
-$x = $childInjector->createInstance('InterfaceX'); // success!!!
-{code}
+<code type="php" title="Parent cannot use child's binders">
+$childInjector = $injector->createChildInjector();
+$childInjector->bindFactory('InterfaceY', 'FactoryY', 'create');
+$y = $injector->createInstance('InterfaceY'); // failure!!!
+</code>

-{code:title=Parent cannot use child's binders}
-$childInjector = $injector->createChildInjector();
-$childInjector->bindFactory('InterfaceY', 'FactoryY', 'create');
-$y = $injector->createInstance('InterfaceY'); // failure!!!
-{code}
+<code type="php" title="Child will return a reference stored on Parent">
+$x = $injector->getInstance('X');
+$childInjector = $injector->createChildInjector();
+$x === $childInjector->getInstance('X'); // true
+</code>

-{code:title=Child will return a reference stored on Parent}
-$x = $injector->getInstance('X');
-$childInjector = $injector->createChildInjector();
-$x === $childInjector->getInstance('X'); // true
-{code}
  Child Injectors allow you to configure sub-modules of code  
differently, without leaking any state into the global scope.



More information about the cvs mailing list