Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5efacd0

Browse files
committed
[DI] Add section about Service Locators
1 parent 7893772 commit 5efacd0

File tree

1 file changed

+227
-0
lines changed

1 file changed

+227
-0
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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 command handlers 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+
public function __construct(array $handlerMap)
29+
{
30+
$this->handlerMap = $handlerMap;
31+
}
32+
33+
public function handle(Command $command)
34+
{
35+
$commandClass = get_class($command)
36+
37+
if (!isset($this->handlerMap[$commandClass])) {
38+
return;
39+
}
40+
41+
return $this->handlerMap[$commandClass]->handle($command);
42+
}
43+
}
44+
45+
// ...
46+
$commandBus->handle(new FooCommand());
47+
48+
Because only one command is handled at a time, other command handlers are not
49+
used but unnecessarily instantiated.
50+
51+
A solution allowing to keep handlers lazily loaded could be to inject the whole
52+
dependency injection container::
53+
54+
use Symfony\Component\DependencyInjection\ContainerInterface;
55+
56+
class CommandBus
57+
{
58+
private $container;
59+
60+
public function __construct(ContainerInterface $container)
61+
{
62+
$this->container = $container;
63+
}
64+
65+
public function handle(Command $command)
66+
{
67+
$commandClass = get_class($command)
68+
69+
if ($this->container->has($commandClass)) {
70+
$handler = $this->container->get($commandClass);
71+
72+
return $handler->handle($command);
73+
}
74+
}
75+
}
76+
77+
But injecting the container has many drawbacks including:
78+
79+
- too broad access to existing services
80+
- services which are actually useful are hidden
81+
82+
Service Locators are intended to solve this problem by giving access to a set of
83+
identified services while instantiating them only when really needed.
84+
85+
Configuration
86+
-------------
87+
88+
For injecting a service locator into your service(s), you first need to register
89+
the service locator itself as a service using the `container.service_locator`
90+
tag:
91+
92+
.. configuration-block::
93+
94+
.. code-block:: yaml
95+
96+
services:
97+
98+
app.command_handler_locator:
99+
class: Symfony\Component\DependencyInjection\ServiceLocator
100+
arguments:
101+
AppBundle\FooCommand: '@app.command_handler.foo'
102+
AppBundle\BarCommand: '@app.command_handler.bar'
103+
tags: ['container.service_locator']
104+
105+
.. code-block:: xml
106+
107+
<?xml version="1.0" encoding="UTF-8" ?>
108+
<container xmlns="http://symfony.com/schema/dic/services"
109+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
110+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
111+
112+
<services>
113+
114+
<service id="app.command_handler_locator" class="Symfony\Component\DependencyInjection\ServiceLocator">
115+
<argument key="AppBundle\FooCommand" type="service" id="app.command_handler.foo" />
116+
<argument key="AppBundle\BarCommand" type="service" id="app.command_handler.bar" />
117+
<tag name="container.service_locator" />
118+
</service>
119+
120+
</services>
121+
</container>
122+
123+
.. code-block:: php
124+
125+
use Symfony\Component\DependencyInjection\ServiceLocator;
126+
use Symfony\Component\DependencyInjection\Reference;
127+
128+
//...
129+
130+
$container
131+
->register('app.command_handler_locator', ServiceLocator::class)
132+
->addTag('container.service_locator')
133+
->setArguments(array(
134+
'AppBundle\FooCommand' => new Reference('app.command_handler.foo'),
135+
'AppBundle\BarCommand' => new Reference('app.command_handler.bar'),
136+
))
137+
;
138+
139+
.. note::
140+
141+
The services defined in the service locator argument must be keyed.
142+
Those keys become their unique identifier inside the locator.
143+
144+
145+
Now you can use it in your services by injecting it as needed:
146+
147+
.. configuration-block::
148+
149+
.. code-block:: yaml
150+
151+
services:
152+
153+
AppBundle\CommandBus:
154+
arguments: ['@app.command_handler_locator']
155+
156+
.. code-block:: xml
157+
158+
<?xml version="1.0" encoding="UTF-8" ?>
159+
<container xmlns="http://symfony.com/schema/dic/services"
160+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
161+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
162+
163+
<services>
164+
165+
<service id="AppBundle\CommandBus">
166+
<argument type="service" id="app.command_handler.locator" />
167+
</service>
168+
169+
</services>
170+
</container>
171+
172+
.. code-block:: php
173+
174+
use AppBundle\CommandBus;
175+
use Symfony\Component\DependencyInjection\Reference;
176+
177+
//...
178+
179+
$container
180+
->register(CommandBus::class)
181+
->setArguments(array(new Reference('app.command_handler_locator')))
182+
;
183+
184+
.. tip::
185+
186+
You should create and inject the service locator as an anonymous service if
187+
it is not intended to be used by multiple services
188+
189+
Usage
190+
-----
191+
192+
Back to our CommandBus which now looks like::
193+
194+
// ...
195+
use Psr\Container\ContainerInterface;
196+
197+
class CommandBus
198+
{
199+
/**
200+
* @var ContainerInterface
201+
*/
202+
private $handlerLocator;
203+
204+
// ...
205+
206+
public function handle(Command $command)
207+
{
208+
$commandClass = get_class($command);
209+
210+
if (!$this->handlerLocator->has($commandClass)) {
211+
return;
212+
}
213+
214+
$handler = $this->handlerLocator->get($commandClass);
215+
216+
return $handler->handle($command);
217+
}
218+
}
219+
220+
The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator`
221+
which implements the PSR-11 ``ContainerInterface``, but it is also a callable::
222+
223+
// ...
224+
$locateHandler = $this->handlerLocator;
225+
$handler = $locateHandler($commandClass);
226+
227+
return $handler->handle($command);

0 commit comments

Comments
 (0)