Skip to content

Commit 36a3f92

Browse files
committed
[DI] Add section about Service Locators
1 parent 7893772 commit 36a3f92

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed
+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
.. index::
2+
single: DependencyInjection; Service Locators
3+
4+
Service Locators
5+
================
6+
7+
What is a Service Locator
8+
-------------------------
9+
10+
Sometimes, a service needs the ability to access other services without being sure
11+
that all of them will actually be used.
12+
13+
In such cases, you may want the instantiation of these services to be lazy, that is
14+
not possible using explicit dependency injection since services are not all meant to
15+
be ``lazy`` (see :doc:`/service_container/lazy_services`).
16+
17+
A real-world example being a CommandBus which maps CommandHandler services by Command
18+
class names and use them to handle their respective command when it is asked for::
19+
20+
// ...
21+
class CommandBus
22+
{
23+
/**
24+
* @var CommandHandler[]
25+
*/
26+
private $handlerMap;
27+
28+
// ...
29+
30+
public function handle(Command $command)
31+
{
32+
if (isset($handlerMap[$commandClass = get_class($command)])) {
33+
$handler = $handlerMap[$commandClass];
34+
35+
return $handler->handle($command);
36+
}
37+
}
38+
}
39+
40+
// ...
41+
$commandBus->handle(new FooCommand());
42+
43+
Because only one command is handled at a time, other CommandHandler services are not
44+
used but unnecessarily instantiated.
45+
46+
Service Locators are intended to solve this problem by giving access to a set of
47+
identified services while instantiating them only when really needed, providing
48+
laziness.
49+
50+
Configuration
51+
-------------
52+
53+
For injecting a service locator in a service, use the `service_locator` argument
54+
type and define the set of services that needs to be accessible from:
55+
56+
.. configuration-block::
57+
58+
.. code-block:: yaml
59+
60+
services:
61+
62+
app.command_bus:
63+
class: AppBundle\CommandBus
64+
arguments:
65+
- =service_locator:
66+
AppBundle\FooCommand: '@app.command_handler.foo'
67+
AppBundle\BarCommand: '@app.command_handler.bar'
68+
69+
.. code-block:: xml
70+
71+
<?xml version="1.0" encoding="UTF-8" ?>
72+
<container xmlns="http://symfony.com/schema/dic/services"
73+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
74+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
75+
76+
<services>
77+
78+
<service id="app.command_bus" class="AppBundle\CommandBus">
79+
<argument type="service-locator">
80+
<argument key="AppBundle\FooCommand" type="service" id="app.command_handler.foo" />
81+
<argument key="AppBundle\BarCommand" type="service" id="app.command_handler.bar" />
82+
</argument>
83+
</service>
84+
85+
</services>
86+
</container>
87+
88+
.. code-block:: php
89+
90+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
91+
use Symfony\Component\DependencyInjection\Reference;
92+
93+
//...
94+
95+
$container
96+
->register('app.command_bus')
97+
->setArguments(array(new ServiceLocatorArgument(array(
98+
'AppBundle\FooCommand' => new Reference('app.command_handler.foo'),
99+
'AppBundle\BarCommand' => new Reference('app.command_handler.bar'),
100+
))))
101+
;
102+
103+
.. note::
104+
105+
The services defined in the service locator argument must be keyed,
106+
as keys become their unique identifier inside the locator.
107+
108+
.. tip::
109+
110+
Not only services can be passed accessible through a service locator,
111+
but all types which are supported by the configuration format used to
112+
configure it.
113+
114+
Usage
115+
-----
116+
117+
Back to our CommandBus which would now look like::
118+
119+
// ...
120+
use Psr\Container\ContainerInterface;
121+
122+
class CommandBus
123+
{
124+
/**
125+
* @var ContainerInterface
126+
*/
127+
private $handlerMap;
128+
129+
// ...
130+
131+
public function handle(Command $command)
132+
{
133+
$commandClass = get_class($command);
134+
$handler = $this->handlerMap->get($commandClass);
135+
136+
return $handler->handle($command);
137+
}
138+
}
139+
140+
The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator`
141+
which implements the PSR-11 ``ContainerInterface`` but is also a callable::
142+
143+
// ...
144+
$handlerMap = $this->handlerMap;
145+
146+
return $handlerMap($handlerId)->handle($command);
147+
148+
And can be used in a loop to iterate over all available services::
149+
150+
// ...
151+
foreach ($this->handlerMap as $commandClass => $handler) {
152+
if ($commandClass === get_class($command)) {
153+
return $handler->handle($command);
154+
}
155+
}

0 commit comments

Comments
 (0)