Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="toc"><ac:parameter ac:name="outline">true</ac:parameter><ac:parameter ac:name="minLevel">2</ac:parameter></ac:macro>

<p>In developing a number of prototypes for the ZF2 MVC, one common issue has presented itself: how do the various layers of the application – controllers, the view, etc – gain access to resources and services they need in order to operate (or to ensure consistent configuration)?</p>

<p>ZF1 has "solved" this with the Bootstrap object, which is injected into the front controller, and then the individual controllers. Action controllers then can pull resources as follows:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$bootstrap = $this->getInvokeArg('bootstrap');
$service = $bootstrap->getResource('someService');
]]></ac:plain-text-body></ac:macro>

<p>While this approach works, it breaks in other areas: plugins, action helpers, and view helpers end up needing to access the front controller singleton in order to gain access to the bootstrap – which is not an ideal situation.</p>

<p>When looking at prototypes, the issue has again raised itself. Consider the following controller:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class FooController extends ActionController
{
public function barAction()

Unknown macro: { $this->view->data = $this->service->doSomething(); }

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

<p>Where is <code>$service</code> defined? We have a few options. In ZF1, you'd typically use the <code>init()</code> or <code>preDispatch()</code> methods:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
public function init()
{
$this->service = new SomeService();
}
]]></ac:plain-text-body></ac:macro>

<p>This isn't terribly great, however, as you likely want to pass in some configuration. To do that, we need to grab the bootstrap, get the appropriate options, and pass them in. And what if we want to test, and mock the service?</p>

<p>So, the better approach is to write mutators and accessors:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
protected $service;

public function barAction()
{
$this->view->data = $this->getService()->doSomething();
}

public function getService()
{
if (null !== $this->service)

Unknown macro: { return $this->service; }

$bootstrap = $this->getInvokeArg('bootstrap');
$options = $bootstrap->getOptions('service');
$this->setService(new SomeService($options));
return $this->service;
}

public function setService(Service $service)
{
$this->service = $service;
return $this;
}
]]></ac:plain-text-body></ac:macro>

<p>This approach has the benefit that we can inject a service object when testing, and also lazy-load the service only when an action needs it. We're still left with an essential problem: how do we get the configuration?</p>

<p>Essentially, the question then, is: within our application code, such as controllers, how do we gain access to required, configurable resources, such as data access components, service clients, etc.?</p>

<p>There are two standard answers to these problems: Service Locators and Dependency Injection.</p>

<p>In the case of Service Locators, we configure our services up front and aggregate them in the locator object (or write a locator that defines and lazy-loads the services on demand). This object is then injected into the layers that need them, and services are pulled out. The ZF1 Bootstrap object is essentially a service locator; the only difference now is formalizing usage of a service locator.</p>

<p>Dependency Injection Containers are a special form of Service Locator that allow you to define what dependencies each class or service has, and ensure that these dependencies are injected. Thus, when you retrieve an object from the DI container, you can ensure that it has all the dependencies it needs. Using this approach, we could either pass a DI container to controllers – or simply retrieve controllers from the DI container, ensuring they have all dependencies immediately upon usage.</p>

<p>We hereby propose inclusion of components offering Service Locator and Dependency Injection functionality.</p>

<h2>Service Locators: Overview</h2>

<p>A Service Locator is incredibly simple: it's simply a registry for objects. Objects are registered with the Locator, and given a common, short name by which they will be referenced. The Service Locator is then injected or consulted in order to retrieve objects.</p>

<p>To illustrate:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
// Seeding the Locator
$locator = new ServiceLocator();
$locator->set('mailer', new Mailer())
->set('db' , $db);

// Later:
$mailer = $locator->get('mailer');
$db = $locator->get('db');
]]></ac:plain-text-body></ac:macro>

<p>In some implementations, explicit methods for each service will exist:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class ApplicationServiceLocator extends ServiceLocator
{
public function getMailer()
{
if (!isset($this->services['mailer']))

Unknown macro: { $this->services['mailer'] = new Mailer(); }

return $this->services['mailer'];
}

public function getDb()
{
if (!isset($this->services['db']))

Unknown macro: { $this->services['db'] = Db}

return $this->services['db'];
}
}
]]></ac:plain-text-body></ac:macro>

<p>These have the benefit of code assist and documentation, as they are explicit code. Typically, a Service Locator implementation will support both explicit setter and getter methods, as well as generic "get" and "set" methods as illustrated earlier. </p>

<p>Service Locators may be static, but most implementations, particularly in PHP, focus on per-instance locators that are then injected into objects that need them.</p>

<p>As an example of using a locator within an object:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class FooController extends ActionController
{
protected $services;

public function __construct(ServiceLocator $services)

Unknown macro: { $this->services = $services; }

public function someAction()

Unknown macro: { $db = $this->services->get('db'); }

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

<p>Service Locators are particularly useful in application paradigms (versus libraries) as they allow configuration of the application in the application bootstrap, and then injection of the service locator into the objects that need them (controllers, views, etc.). </p>

<p>The chief drawback is that they often require a fair amount of explicit coding – either extending the base ServiceLocator class, or configuring and seeding it in the application bootstrap.</p>

<h2>Dependency Injection: Overview</h2>

<p>Dependency Injection is really quite simple. You design your class in such a way that dependencies are injected either in the constructor or via setters; often these will be paired with lazy-loading so that if nothing is injected, sane defaults are used. The Service Locator example already shows this to a degree – the Service Locator is injected when the <code>FooController</code> class is instantiated.</p>

<p>As a more complete example, the original bad example could be refactored as follows:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class FooController extends ActionController
{
protected $foo;
protected $bar;
protected $db;
protected $service;

public function __construct($foo, $bar)

Unknown macro: { $this->foo = $foo; $this->bar = $bar; }

public function setDb(DBAdapter $db)

Unknown macro: { $this->db = $db; return $this; }

public function getDb()
{
if (null === $this->db)

Unknown macro: { $this->db = Db}

return $db;
}

public function setService(Twitter $service)

Unknown macro: { $this->service = $service; return $this; }

public function getService()
{
if (null === $this->service)

Unknown macro: { $this->service = new TwitterClient('foouser', 'foopass'); }

return $service;
}

public function someAction()

Unknown macro: { // Grabbing items injected via constructor, and thus assumed as // available $foo = $this->foo; $bar = $this->bar; // Grabbing items with explicit getters $db = $this->getDb(); $service = $this->getService(); }

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

<p>In order to use this class, then, you need to pass in valid arguments to the constructor, and optionally setters:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$controller = new FooController('foo', 'bar');
$controller->setDb($db)
->setService($twitter);
$controller->someAction();
]]></ac:plain-text-body></ac:macro>

<p>This solves several problems, but introduces a new one: where and when does injection happen? Often, such objects are created within the application architecture, and we have no control over it.</p>

<p>As such, a common solution is to combine Dependency Injection with a Service Locator. When a request is made for a service, if a service <em>definition</em> has been created, it is instantiated and returned.</p>

<p>A service definition typically consists of several things:</p>

<ul>
<li>The actual class name being referenced.</li>
<li>Constructor arguments (if any), and the values you wish to use (if any). These may be <em>references</em> to other services.</li>
<li>Optionally, a list of setters or other methods you wish to inject. In the above code, <code>setDb</code> and <code>setService</code> are considered setters, and the definition might define these methods so that they may be injected with values from the container.</li>
</ul>

<p>As such, the typical setup consists of:</p>

<ul>
<li>One or more Definitions (configuration), which are then passed to...</li>
<li>A Dependency Injection manager, which then seeds...</li>
<li>A Service Locator.</li>
</ul>

<p>The benefit of tying a Service Locator to a Dependency Injection manager is that you wire all your dependencies together. For instance, if you have an <code>EntryService</code> that consumes a data access object, which in turn consumes a specific database connection, you might do the following:</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$di => $serviceLocator->getInjector();
$conn = new Definition('Mongo');
$conn->setParam('server', 'mongodb://foohost:27017')
->setParamMap(array('server' => 0));
$di->setDefinition('cxn', $conn);

$dal = new Definition('my\Dbal\Mongo');
$dal->addMethodCall('setConnection', array(new Reference('cxn')));
$di->setDefinition('dal', $dal);

$entries = new Definition('my\Service\Entry');
$entries->setParam('dataAccess', new Reference('dal'))
->setParamMap(array('dataAccess' => 0));
$di->setDefinition('entries', $entries);

// and to retrieve an entries object:
$entries = $serviceLocator->get('entries');
]]></ac:plain-text-body></ac:macro>

<h2>Requirements</h2>

<ul>
<li><strong>MUST</strong> contain separate interfaces for:
<ul>
<li>Service Locators</li>
<li>Dependency Injection managers</li>
<li>Rationale: In most cases, a service locator is all consuming code really needs to utilize. As an example, a controller or view object might receive the service locator as an argument, and pull services from it as needed. The dependency injection aspects may be assumed, or it may be assumed the service objects are fully configured when placed in the container. Additionally, configuration of the DI definitions is usually done once, at application initialization; typehinting on the DI aspects then becomes a moot point.</li>
</ul>
</li>
<li>Service Locator
<ul>
<li><strong>MUST</strong> allow injecting objects using short names
<ul>
<li><strong>COULD</strong> allow registering callbacks/closures that return an object</li>
</ul>
</li>
<li><strong>MUST</strong> allow retrieving objects using the short names provided at registration
<ul>
<li><strong>SHOULD</strong> allow passing an array of constructor arguments that will be used (these would be passed on to the underlying DI container, if any)</li>
<li>If the solution allows registering callbacks/closures, the<br />
return value of that operation would be returned</li>
</ul>
</li>
<li><strong>SHOULD</strong> provide a DI-enabled implementation</li>
</ul>
</li>
<li>Dependency Injection
<ul>
<li><strong>MUST</strong> allow configurable service definitions
<ul>
<li>Definitions:
<ul>
<li><strong>MUST</strong> specify the class</li>
<li><strong>MUST</strong> allow specifying named constructor arguments
<ul>
<li><strong>MUST</strong> allow manually specifying argument order</li>
<li><strong>SHOULD</strong> allow using Reflection to determine argument order
<ul>
<li><strong>SHOULD</strong> allow caching the definition such that Reflected arguments return an argument name => order map</li>
</ul>
</li>
</ul>
</li>
<li><strong>MUST</strong> allow specifying setters and other methods
<ul>
<li><strong>MUST</strong> allow specifying all arguments to these methods</li>
<li><strong>COULD</strong> allow specifying named arguments</li>
</ul>
</li>
<li><strong>MUST</strong> allow specifying whether new instances should always be returned
<ul>
<li><strong>MUST</strong> default to using shared instances (i.e., maintaining a registry of named services)</li>
</ul>
</li>
<li><strong>MUST</strong> allow <em>referencing</em> other services for purposes of DI
<ul>
<li>When instantiating or calling the method receiving the argument, the service will then be retrieved from the container.</li>
</ul>
</li>
<li><strong>COULD</strong> allow specifying "tags"
<ul>
<li>All services "tagged" could be retrieved and iterated</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>COULD</strong> allow specifying aliases
<ul>
<li>When an alias is encountered, the service it references would be returned instead</li>
</ul>
</li>
<li><strong>MUST</strong> provide a method for retrieving object instances
<ul>
<li>The method <strong>MUST</strong> accept a class name as the first parameter
<ul>
<li>If the class name does not match a definition, the container would simply return a new instance of that class</li>
</ul>
</li>
<li>The method <strong>SHOULD</strong> allow specifying an associative array of arguments
<ul>
<li>These arguments would be merged with any constructor arguments, and override those in any definitions.</li>
<li><strong>SHOULD</strong> allow specifying method arguments as well</li>
</ul>
</li>
<li>For any <em>referenced</em> services, the manager <strong>MUST</strong> retrieve the given service and inject according to the Definition.</li>
</ul>
</li>
</ul>
</li>
</ul>

<h2>Interfaces</h2>

<ul>
<li>Service Locator
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
interface ServiceLocation
{
public function set($serviceName, $service);
public function get($serviceName, array $params = null);
}
]]></ac:plain-text-body></ac:macro></li>
<li>Dependency Injection Manager
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
interface DependencyInjection
{
public function get($name, array $params = array();
public function newInstance($name, array $params = array();

/**

  • @param array|Traversable $definitions Iterable Definition objects
    */
    public function setDefinitions($definitions);

public function setDefinition(DependencyDefinition $definition, $serviceName = null);
public function setAlias($alias, $serviceName);
}
]]></ac:plain-text-body></ac:macro></li>
<li>Service Definition
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
interface DependencyDefinition
{
public function __construct($className);
public function getClass();

public function setParam($name, $value);
public function setParams(array $params);
/**

  • @param array $map Map of name => position pairs for constructor arguments
    */
    public function setParamMap(array $map);
    public function getParams();

public function setShared($flag = true);
public function isShared();

public function addTag($tag);
public function addTags(array $tags);
public function getTags();
public function hasTag($tag);

public function addMethodCall($name, array $args);

/**

  • @return MethodCollection
    */
    public function getMethodCalls();
    }
    ]]></ac:plain-text-body></ac:macro></li>
    <li>Method Collection
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    interface InjectibleMethods extends Iterator
    {
    public function insert(InjectibleMethod $method);

/**

  • @return InjectibleMethod
    */
    // public function current();

/**

  • @return string Method name
    */
    // public function key();
    }
    ]]></ac:plain-text-body></ac:macro></li>
    <li>Method Definition
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    interface InjectibleMethod
    {
    public function __construct($name, array $args);
    public function getName();
    public function getArgs();
    }
    ]]></ac:plain-text-body></ac:macro></li>
    <li>Reference
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    interface DependencyReference
    {
    public function __construct($serviceName);
    public function getServiceName();
    }
    ]]></ac:plain-text-body></ac:macro></li>
    <li>DI-enabled
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    interface DependencyEnabled
    {
    public function setInjector(DependencyInjection $di);
    public function getInjector();
    }
    ]]></ac:plain-text-body></ac:macro></li>
    </ul>

<h2>Implementations</h2>

<p>In addition to the above interfaces, ZF2 would provide standard implementations of each. In particular, a DI-enabled Service Locator implemenation would be provided; the DI container would allow seeding of definitions via configuration.</p>

<p>The Service Locator implementation would also provide capabilities for extending the implementation to provide explicit getter methods for services, and the ability to map services to these methods.</p>

<h3>Usage</h3>

<ul>
<li>Basic service locator:
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$services = new ServiceLocator();

// Registering an object:
$services->set('db', $db);

// Lazy-loading by registering a closure:
$services->set('db', function() use ($config) {
$db = Db::factory($config->db);
return $db;
});

// Retrieving:
$db = $services->get('db');
]]></ac:plain-text-body></ac:macro></li>
<li>Extending the service locator:
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class CustomLocator extends ServiceLocator()
{
protected $serviceMap = array(
'db' => 'Database',
);

public function getDatabase()
{
if (!$db = $this->get('db'))

Unknown macro: { $db = Db}

return $db;
}

public function setDatabase(DB\Adapter $db)

Unknown macro: { $this->set('db', $db); }

}

$services = new CustomLocator();

// Generically:
$db = $services->get('db')

// Specifically:
$db = $services->getDatabase();

// Setting:
$services->set('db', $db);
$services->setDatabase($db);
]]></ac:plain-text-body></ac:macro></li>
<li>DI-enabled service locator:
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$di = new DependencyInjector($config);
$services = new DiServiceLocator();
$services->setInjector($di);
$db = $services->get('db');
]]></ac:plain-text-body></ac:macro></li>
<li>DI definitions.
<ul>
<li>Assume the following definitions
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class EntryService
{
public function setDataAccess(DataAccess $dataAccess) {}
}

class MongoDataAccess implements DataAccess
{
public function __construct(Mongo $connection) {}
}

// and the Mongo class from ext/mongo
]]></ac:plain-text-body></ac:macro>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
$mongo = new Definition('Mongo');
$mongo->setParam('server', 'mongodb://staging:27017')
->setParamMap(array('server' => 0));
$di->setDefinition('connection', $mongo);

$dataAccess = new Definition('MongoDataAccess');
$dataAccess->setParam('connection', new Reference('connection'));
$di->setDefinition('access', $dataAccess);

$entries = new Definition('EntryService');
$entries->addMethodCall('setDataAccess', array(new Reference('access')));
$di->setDefinition('entries', $entries);

// Add DI to service locator
$services->setInjector($di);

// Retrieve "entries" service
$entries = $services->get('entries');
]]></ac:plain-text-body></ac:macro></li>
</ul>
</li>
</ul>

<h3>MVC Usage</h3>

<p>This proposal began with a discussion of the MVC use case. The following examples show different ways that Service Locators or DI Containers could be used to solve a common issue found in MVC applications: how do the various layers of the application receive their dependencies?</p>

<p>Two solutions present themselves. In the first case, the Front Controller could compose a Service Locator instance, and pass it to the constructor of controllers it instantiates. This would allow each controller to pull dependencies and pass them into various service objects, view objects, and domain entities.</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class FooController extends ActionController
{
protected $services;

public function __construct(ServiceLocator $services = null)
{
if (null !== $services)

Unknown macro: { $this->services = $services; }

else

Unknown macro: { $this->services = new ServiceLocator; }

}

public function getFooService()

Unknown macro: { $service = new FooService(); $service->setDataAccess($this->services->get('data-access')); }

public function fooAction()

Unknown macro: { $foo = $this->getFooService(); $foo->doSomething(); }

}

class FrontController implements Dispatchable
{
protected $services;

public function setServiceLocator(ServiceLocator $services)

Unknown macro: { $this->services = $services; return $this; }

public function getServiceLocator()
{
if (null === $this->services)

Unknown macro: { $this->setServiceLocator(new ServiceLocator()); }

return $this->services;
}

public function dispatch(Request $request, Response $response = null)

Unknown macro: { // ... $controller = new $controllerName($this->getServiceLocator()); $result = $controller->dispatch($request, $response); // ... }

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

<p>In the second case, controllers could define required components via constructor arguments or setters, and the front controller would then retrieve the controllers via a DI container, ensuring that all dependencies are injected.</p>

<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
class FooService
{
protected $dataAccess;

public function setDataAccess(DataAccess $data)

Unknown macro: { $this->dataAccess = $data; return $this; }

public function getDataAccess()

Unknown macro: { return $this->dataAccess; }

}

class FooController extends ActionController
{
protected $foo;
protected $services;

public function setServiceLocator(ServiceLocator $services)

public function setFooService(FooService $foo)

Unknown macro: { $this->foo = $foo; }

public function fooAction()

Unknown macro: { $foo = $this->foo; $foo->doSomething(); }

}

class FrontController implements Dispatchable
{
protected $services;

public function setServiceLocator(ServiceLocator $services)

Unknown macro: { $this->services = $services; return $this; }

public function getServiceLocator()
{
if (null === $this->services)

Unknown macro: { $this->setServiceLocator(new ServiceLocator()); }

return $this->services;
}

public function dispatch(Request $request, Response $response = null)

Unknown macro: { // ... $controller = $this->getServiceLocator()->get($controllerName); $result = $controller->dispatch($request, $response); // ... }

}

$di = new DependencyInjector();
$di->setDefinitions($config->di);
$services = new ServiceLocator();
$services->setLocator($di);

$front = new FrontController();
$front->setServiceLocator($services);
$response = $front->dispatch();
$response->send();
]]></ac:plain-text-body></ac:macro>

<p>In the case of composing a Service Locator, the convention would be that controllers would optionally accept a Service Locator in their constructor, or via a setter. In the case of retrieving controllers via a DI container, we would recommend that controllers have a setter for the DI container itself, so that they may retrieve other controllers and ensure they have all dependencies satisfied.</p>

<h2>References</h2>

<p>A prototype implementation using the interfaces listed in this proposal has been created:</p>

<ul>
<li><a href="https://github.com/weierophinney/zf-examples/tree/projects%2Fzf2.di/zf2-di">https://github.com/weierophinney/zf-examples/tree/projects%2Fzf2.di/zf2-di</a></li>
</ul>

<p>The MVC prototype I was working off of that spurred this proposal:</p>

<ul>
<li><a href="http://git.mwop.net/?a=summary&p=zf2sandbox">http://git.mwop.net/?a=summary&amp;p=zf2sandbox</a></li>
</ul>

<p>Some literature on Service Locators and Dependency Injection:</p>

<ul>
<li><a href="http://en.wikipedia.org/wiki/Hollywood_principle">http://en.wikipedia.org/wiki/Hollywood_principle</a></li>
<li><a href="http://martinfowler.com/articles/injection.html">http://martinfowler.com/articles/injection.html</a></li>
<li>Symfony 2 Dependency Injection: <a href="http://components.symfony-project.org/dependency-injection/">http://components.symfony-project.org/dependency-injection/</a></li>
<li>Aura PHP DI: <a href="https://github.com/auraphp/aura.di">https://github.com/auraphp/aura.di</a></li>
<li>Lithium talk from tek-x: <a href="http://www.slideshare.net/jperras/tekx-a-framework-for-people-who-hate-frameworks-lithium">http://www.slideshare.net/jperras/tekx-a-framework-for-people-who-hate-frameworks-lithium</a></li>
<li>A bunch of Java and .NET stuff I really don't want to link to</li>
</ul>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Mar 09, 2011

    <p>Looks extremely straight-forward. Nice work.</p>

  2. Mar 13, 2011

    <p>It would be good to see how complex the DIC configuration of the framework would become using this approach, hopefully we wouldn't end up with a huge configuration that was hard to track/learn. </p>

    <p>Also how would cyclic dependencies be handled and the size of the object graph? Is this proposal for framework level DI/SL or userland as well?</p>

  3. Mar 18, 2011

    <p>Great work. </p>

    <p>I was wondering... if this is implemented in ZF2, what would be the difference between ZF2 and Symfony2?</p>