[dev] Injector musings

Chuck Hagenbuch chuck at horde.org
Sun Mar 21 20:13:22 UTC 2010


Quoting Michael M Slusarz <slusarz at horde.org>:

> Ugh... finally getting around to responding.

Ditto? :/

> Quoting Chuck Hagenbuch <chuck at horde.org>:
>
>> Quoting Michael M Slusarz <slusarz at horde.org>:
>>
>>> 2.) How do you create an instance with an injector if that  
>>> instance's constructor has varied arguments?  This seems to be a  
>>> limitation of Horde_Injector... is this by design or simply  
>>> because nobody has implemented it yet.
>>
>> The latter, if I'm following. What I want to add to the injector  
>> (that came from a completely separate conversation with one of the  
>> folks who wrote the initial code) is setter injection. In the case  
>> of the Mime_Viewer objects, different viewers would have setFoo()  
>> methods for the dependencies that aren't in the constructor (common  
>> to all Mime_Viewers). The injector would then satisfy those  
>> dependencies as well on object creation.
>>
>>> For example, Horde configuration needs to be decoupled from  
>>> Horde_Mime_Viewer.  However, every Horde_Mime_Viewer instance does  
>>> not need every single configuration option passed to it that might  
>>> potentially be used by the drivers.  On the other hand, the  
>>> drivers may create other Horde_Mime_Viewers internally that may  
>>> need config options not needed by the original driver.
>>
>> For this last case the viewer should have a reference to the  
>> injector. Using setter injection this could be accomplished with a  
>> setInjector(Horde_Injector $injector) method; the injector would  
>> know to pass itself to this when creating the object.
>>
>>> Regardless, it is very useful to keep the instance around since it  
>>> might be used elsewhere in the code.  A better example of this  
>>> might be IMP_Contents.  Once initialized, it may be used in  
>>> various locations around the code that are not accessible to each  
>>> other.  How does this work with injector usage?  Or is this the  
>>> motivation to retain usage of singletons in this usage pattern?
>>
>> I'm not sure I entirely follow the question. If the object  
>> instantiation/initialization is complicated, then a binder or  
>> factory for it seems appropriate. You can then call getInstance()  
>> wherever you need it, and the object will be created and  
>> initialized only once.
>
> Not sure if I am following here.  Maybe a better example is  
> IMP_Contents.  We only want to instantiate one IMP_Contents object  
> for each message.  So, for UID 1 in INBOX, today we have the  
> following call:
>
> $contents = IMP_Contents::singleton(1 . IMP::IDX_SEP . 'INBOX');
>
> However, we will most likely use this same object in various  
> locations throughout IMP.  We don't want to create a new object -  
> this is just a waste of resources.
>
> Currently, the best we can do (I think) is as follows:
>
> class IMP_Contents {
>   ...
>   function foo(Horde_Injector $injector) {
>     return new IMP_Contents($injector->getInstance('UID'));
>   }
>   ...
> }
>
> $injector->bindFactory('IMP_Contents', 'IMP_Contents', 'foo');
> $injector->setInstance('UID', 1 . IMP::IDX_SEP . 'INBOX');
> $contents = $injector->getInstance('IMP_Contents');
>
> That's not optimal since we are removing the UID definition from the  
> actual getInstance() call.  But that's not a deal breaker.
>
> Where things get a bit hackish is when we need to load UID 2 in the  
> same session access.  This won't work:
> $injector->setInstance('UID', 2 . IMP::IDX_SEP . 'INBOX');
> $contents2 = $injector->getInstance('IMP_Contents');
>
> Because $contents2 will be the object containing UID 1.  And I don't  
> want to use $injector->createInstance() since I want this newly  
> created instance to be available later (the whole point of a  
> singleton).
>
> Thus, the need for something like:
> $contents = $injector->getInstance('IMP_Contents', 1 . IMP::IDX_SEP  
> . 'INBOX');
> $contents2 = $injector->getInstance('IMP_Contents', 2 . IMP::IDX_SEP  
> . 'INBOX');
>
> Then later on, when I call
> $injector->getInstance('IMP_Contents', 1 . IMP::IDX_SEP . 'INBOX');
>
> I will get the $contents object that was previously instantiated.

Ah, okay. So I think what's called here for is a factory. Something like:

$contentsFactory = $injector->getInstance('IMP_Contents_Factory');
$contents1 = $contentsFactory->getContents(1, 'INBOX');
$contents2 = $contentsFactory->getContents(2, 'INBOX');

... etc. The factory can internally manage whether or not a contents  
object has been created before.

Also, in general we shouldn't be passing the injector around outside  
of the bootstrap/controller layer. Instead, for the Contents example,  
code that needs to create IMP_Contents objects should require an  
IMP_Contents_Factory in its contructor, not the injector directly.

-chuck


More information about the dev mailing list