compared with
Current by Ralph Schindler
on Mar 23, 2012 22:03.

(show comment)
Key
This line was removed.
This word was removed. This word was added.
This line was added.

Changes (208)

View Page History
h1. Zend\ServiceLocator RFC
<h1>Zend\ServiceLocator RFC</h1>

<p>Currently, Zend\Di is being used as both a DI Container (DiC) as well as a Service Locator (SL) for sharing services (objects who are shared and are dependencies of other objects in other parts of an application). This presents a problem in that when ZF2 modules are written, an assumption that a DiC based Service Locator is available as opposed to simply a Service Locator. Since the minimally assumed interface is a DiC presenting itself as a SL, a module writer is making assumptions about the implementation details of a DiC being present in all applications.</p>

h2. Some Concepts & History
<h2>Some Concepts &amp; History</h2>

<p>DiC (Zend\Di), is a complex component. This is not to say this is true of Zend\Di specifically, but all Di Containers in general. DiC requires intimate knowledge of the set of classes that make up the code base from which instances are produced. They must know class structure including method signatures, interface hints, supertype/subtype relationships, which parameters represent dependencies and which parameters represent static or configuration based information and of those, which are required and which are optional. Any DiC solution that does not require a developer to write an excessive amount of "wiring information" &quot;wiring information&quot; is going to be less performant in PHP. If a DiC solution does require lots of "wiring information" &quot;wiring information&quot; then there is nothing to gain from the DiC solution itself, as you can simply write the wirings themselves.</p>

<p>DiC's are sexy solutions because they take information in the form of configuration and through a magical set of processes (to the casual observer/consumer), have the ability to produce instances on demand. It has the added benefit of storing these instances inside a registry for shared usage and also presents then in a lazy-loadable fashion. These concepts, in and of themselves are beneficial to application writers.</p>

<p>It is even more important to know where DiC solutions originated: in the .Net and Java world. In both of these platforms, there is a long-lived process where the DiC is statically stored and available to child-threads (web requests). The tradeoff is that however long it takes to build up the rich set of information/definitions required to be able to create instances on demand, this is paid up front on "application &quot;application startup time", time&quot;, as opposed to each thread paying the price of DiC startup.</p>

<p>In PHP, this DiC startup cost is paid on every request. And, in any DiC solution, the more information it is ultimately responsible for, the more of a startup cost said DiC solution will have in each and every request to the PHP application.</p>

<p>Also specific to PHP, configuration is not op-code cacheable in the same way that actual code is and fewer performance gains can be attained by throwing an op-code cache at an application that is entirely dependent on a configured DiC.</p>

h2. Proposal for Zend\ServiceLocator
<h2>Proposal for Zend\ServiceLocator</h2>

<p>To build a ServiceLocator component (as Zend\ServiceLocator), this is a standalone component- it has no hard dependencies on any other components in the Zend Framework. There exists one integration point for utilizing Zend\Di (DiC) as a resource for retrieving objects to present as services.</p>

h3. Objectives and responsibilities:
<h3>Objectives and responsibilities:</h3>

* Ability to alias services with different names
<ul>
<li>Ability to alias services with different names</li>
* Ability <li>Ability to Lazy-Load services, and provide infrastructure to accomplish this</li>
* Ability <li>Ability to validate the types (classes) of services to their respective names</li>
* Ability <li>Ability to inject the ServiceLocator into services that require Locator awareness</li>
* Ability <li>Ability to OPTIONALLY pull objects from a DiC and present them as services
<ul>
** Ability <li>Ability for this Di based service to utilize a service locator (who's services are not part of a DiC to be used)</li>
</ul>
</li>
</ul>

h3. Interfaces:

h4. ServiceLocatorInterface:
<h3>Interfaces:</h3>

* A simple interface that allows for the 4 primary tasks:
** setting services
** retrieving services
** checking for a service by name
** creating aliases of existing services or aliases
<h4>ServiceLocatorInterface:</h4>

This interface ensures that any one SL can easily be swapped out with another SL provided they implement this interface.
<ul>
<li>A simple interface that allows for the 4 primary tasks:
<ul>
<li>setting services</li>
<li>retrieving services</li>
<li>checking for a service by name</li>
<li>creating aliases of existing services or aliases</li>
</ul>
</li>
</ul>

{code}
<p>This interface ensures that any one SL can easily be swapped out with another SL provided they implement this interface.</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php
namespace Zend\ServiceLocator {

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

{note}
<ac:macro ac:name="note"><ac:rich-text-body>
<p>Important note on get(). By definition, a service is a self contained object. Regardless if it is lazy loaded, or provided to the service locator via set(), get() does not provide the ability to attain "variations" &quot;variations&quot; on purpose. Since services are shared, variations should exist by their own name (the aliasing) and should be declared up front, so that each consumer can say with certainty that a particular service (by-name) has the same stateful setup. Trying to pull a variation at runtime (as you can with Di) is discouraged when objects are viewed as "services" &quot;services&quot; and locatable by a service locator.</p></ac:rich-text-body></ac:macro>
{note}

h4. Other interfaces:
<h4>Other interfaces:</h4>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php


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

h4. Other facilities:
<h4>Other facilities:</h4>

h5. Validator
<h5>Validator</h5>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php


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

h5. For Di based Service Integration
<h5>For Di based Service Integration</h5>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php


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

h3. Examples
<h3>Examples</h3>

h4. Basic setting/getting of service
<h4>Basic setting/getting of service</h4>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\ServiceLocator\ServiceLocator;
$sl = new ServiceLocator;
$sl->set('myservice', new \stdClass);
$myservice = $sl->get('myservice');
{code}
]]></ac:plain-text-body></ac:macro>

h4. Basic setting of service, wrapped in closure for lazy-loading
<h4>Basic setting of service, wrapped in closure for lazy-loading</h4>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\ServiceLocator\ServiceLocator;
$sl = new ServiceLocator;
$sl->set('myservice', function () { return new \stdClass; });
$myservice = $sl->get('myservice'); // closure invoked here, replaces value of myservice internally
{code}
]]></ac:plain-text-body></ac:macro>

h4. ServiceLocator awareness
<h4>ServiceLocator awareness</h4>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\ServiceLocator\ServiceLocator,
Zend\ServiceLocator\ServiceLocatorAwareInterface,
$sl->set('myservice', new ServiceAwareService);
assert($sl === $sl->get('myservice')->sl);
{code}
]]></ac:plain-text-body></ac:macro>

h5. A purely lazy service wrapper
<h5>A purely lazy service wrapper</h5>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
use Zend\ServiceLocator\LazyServiceInterface,
Zend\ServiceLocator\ServiceLocator;
$sl->set('db', new \DbService($config));
assert($sl->get('db') instanceof \Zend\Db\Adapter); // true;
{code}
]]></ac:plain-text-body></ac:macro>


h2. Proposal for Zend\Mvc & Zend\Module integration
<h2>Proposal for Zend\Mvc &amp; Zend\Module integration</h2>

<p>Zend\ServiceLocator should become the "first &quot;first class citizen" citizen&quot; in the Zend\Mvc application infrastructure (as opposed to Zend\Di). What does that mean? It means that instead for forcing Zend\Di as the primary means of sharing services between parts of an application (local or 3rd party), that a more lightweight component (smaller footprint in both code as well as runtime implications) act as the primary interface for sharing these services. In terms of Zend\Mvc and Zend\Module, Zend\Mvc will have the well-known service location object composed in, and Zend\Module will provide features that allow application & &amp; 3rd party modules to interact with this ServiceLocator.</p>

h3. Changes in Zend\Mvc\Application:
<h3>Changes in Zend\Mvc\Application:</h3>

<ul>
* Instead <li>Instead of composing Zend\Di, Zend\ServiceLocator will be composed (probably created by default)</li>
</ul>

h3. Zend\Module will add Zend\Module\Feature\Service.

{note}
Suggetion: Zend\Module\Consumer renamed to Zend\Module\Feature as modules that interact with the service locator will not only consume application resources, but might also provide application resources (such as services). Since that is the case, I suggest renaming Consumer to Feature as it is more applicable as to the role of this aspect of Zend\Module.
{note}
<h3>Zend\Module will add Zend\Module\Feature\Service.</h3>

* Ability to provide services and aliases from the Module class
* Ability to provide a map of names to types for service validation
** in order to use services by name, they should first pass type validation so that modules can be sure that objects of a particular service name are of a known type
<ac:macro ac:name="note"><ac:rich-text-body>
<p>Suggetion: Zend\Module\Consumer renamed to Zend\Module\Feature as modules that interact with the service locator will not only consume application resources, but might also provide application resources (such as services). Since that is the case, I suggest renaming Consumer to Feature as it is more applicable as to the role of this aspect of Zend\Module.</p></ac:rich-text-body></ac:macro>

<ul>
<li>Ability to provide services and aliases from the Module class</li>
<li>Ability to provide a map of names to types for service validation
<ul>
<li>in order to use services by name, they should first pass type validation so that modules can be sure that objects of a particular service name are of a known type</li>
</ul>
</li>
</ul>

h4. Interfaces:

{code}
<h4>Interfaces:</h4>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php


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

h4. Facilities (objects that are returned when using above interfaces):
<h4>Facilities (objects that are returned when using above interfaces):</h4>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

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

h4. Examples: Usage Inside Modules
<h4>Examples: Usage Inside Modules</h4>

A Module class example.
<p>A Module class example.</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

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

Utilization inside a controller:
<p>Utilization inside a controller:</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

<?php

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

h4. Questions and Answers
<h4>Questions and Answers</h4>

h5. Question
<h5>Question</h5>
<p>How do I get configuration into the various services?</p>

<p>As an example, in development, I might use a "File" &quot;File&quot; mail transport, and need to specify the path to which to write files, but in production, I might configure an "SMTP" &quot;SMTP&quot; mail transport, and need to pass in SMTP-Auth credentials. Additionally, it's typically easiest to aggregate all application configuration at once, versus piecemeal, by service. How can I pass application configuration to the Service Locator, and then get at that configuration when creating my services?</p>

h5. Answer
<h5>Answer</h5>

<p>First, it is important to know that configuration itself can be a service, after all,<br />
we treat it as a "dependency". &quot;dependency&quot;.</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

$dbAdapter = $serviceLocator->get('dbAdapter');

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

Taking this a step further:
<p>Taking this a step further:</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

$mailer->send($message);

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

<p>Taking this example even further, we can demonstrate that services don't have<br />
to be Closure objects. They can be "Service objects". &quot;Service objects&quot;.</p>

<p>Service Objects exist as a way to lazily create services as needed. They should<br />
remain extremely lightweight.</p>

<p>These Service Objects can be hand coded and shipped, or generated by something like<br />
Zend\Di\Introspection\ServiceGenerator:</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

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

h5. Question
Can I mix DI and Service Location?
<h5>Question</h5>
<p>Can I mix DI and Service Location?</p>

I <p>I might want to fallback to DI, or pull dependencies out of DI. Some of those may have dependencies I've defined in my Service Locator, however, while others may not. How can I handle such a mixed strategy? Related: how can I get DI configuration into the DI container when used in this fashion?</p>

h5. Answer
<h5>Answer</h5>
<p>The DiC instance is something separate from the Service Locator. To continue to use a DiC with the service locator, you would use a Di based Service:</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

$serviceLocator->set('instancename', $diService($zendDiInstance, 'instancename');

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


h5. Question
<h5>Question</h5>
What about controllers?

Currently, controllers are pulled from the locator, with the idea of emphasizing dependency injection over service location for gaining access to dependencies. However, with the DIC, we can often omit configuration for individual controllers when they have no dependencies or when dependencies can by auto-discovered. Will this approach be possible with a Service Locator, possibly by having a DI-aware Locator? If so, how?
<p>What about controllers?</p>

h5. Answer
<p>Currently, controllers are pulled from the locator, with the idea of emphasizing dependency injection over service location for gaining access to dependencies. However, with the DIC, we can often omit configuration for individual controllers when they have no dependencies or when dependencies can by auto-discovered. Will this approach be possible with a Service Locator, possibly by having a DI-aware Locator? If so, how?</p>

Controllers are, as we've seen, a special case.
<h5>Answer</h5>

We want all the benefits of injecting services and DI for our controllers, but we don't
necessarily want to expose our controllers as "services" application wide.
<p>Controllers are, as we've seen, a special case.</p>

The answer to this is scoped containers or, scoped Service Locators.
<p>We want all the benefits of injecting services and DI for our controllers, but we don't<br />
necessarily want to expose our controllers as &quot;services&quot; application wide.</p>

<p>The answer to this is scoped containers or, scoped Service Locators.</p>
{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

$controller->indexAction();

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


h5. Question
<h5>Question</h5>

<p>My module may create artifacts based on calculations that I want available elsewhere in my application; how can I get these into the service locator? How would I access them elsewhere? (This one should show both injecting such artifacts, as well as pulling them from a locator.)</p>

h5. Answer
<h5>Answer</h5>

<p>It's important to remember, just about any object or array can be a service:</p>

{code}
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
<?php

$calculationArray = $serviceLocator->get('mycool_calculations');

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

h5. Question
<h5>Question</h5>

<p>Can the Service Locator return different instances of a service? As an example, EventManager instances should be discrete per-class, but each should be injected with a shared SharedEventManager instance. Can this be done with the Service Locator?</p>

h5. Answer
<h5>Answer</h5>

<p>It is important to remember Service Locators do not instantiate objects for you, it is up to the<br />
Service Object (or Closure), to instantiate the object. The Service Locator itself does not care<br />
how a particular service is created or how (if at all) its dependencies are injected.</p>

<p>That said, it might make sense to consider moving the "shared" &quot;shared&quot; support from DI to the ServiceLocator.</p>

h5. Question
<h5>Question</h5>

<p>How can a module define services for the application's service locator instance? (This is in the RFC already.)</p>

h5. Answer
<h5>Answer</h5>

See the above section.
<p>See the above section.</p>