Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ca9f215

Browse filesBrowse files
committed
minor #7458 [DI] Add section about service locators (chalasr, javiereguiluz)
This PR was merged into the master branch. Discussion ---------- [DI] Add section about service locators Adds documentation for symfony/symfony#21553 and symfony/symfony#22024. Any suggestion will be much appreciated, as usual. Commits ------- fa19770 Fix service locator declaration f5e4942 Rewords 5efacd0 [DI] Add section about Service Locators
2 parents 0e26110 + fa19770 commit ca9f215
Copy full SHA for ca9f215

File tree

1 file changed

+226
-0
lines changed
Filter options

1 file changed

+226
-0
lines changed
+226Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
.. index::
2+
single: DependencyInjection; Service Locators
3+
4+
Service Locators
5+
================
6+
7+
Sometimes, a service needs access to several other services without being sure
8+
that all of them will actually be used. In those cases, you may want the
9+
instantiation of the services to be lazy. However, that's not possible using
10+
the explicit dependency injection since services are not all meant to
11+
be ``lazy`` (see :doc:`/service_container/lazy_services`).
12+
13+
A real-world example are applications that implement the `Command pattern`_
14+
using a CommandBus to map command handlers by Command class names and use them
15+
to handle their respective command when it is asked for::
16+
17+
// ...
18+
class CommandBus
19+
{
20+
/**
21+
* @var CommandHandler[]
22+
*/
23+
private $handlerMap;
24+
25+
public function __construct(array $handlerMap)
26+
{
27+
$this->handlerMap = $handlerMap;
28+
}
29+
30+
public function handle(Command $command)
31+
{
32+
$commandClass = get_class($command)
33+
34+
if (!isset($this->handlerMap[$commandClass])) {
35+
return;
36+
}
37+
38+
return $this->handlerMap[$commandClass]->handle($command);
39+
}
40+
}
41+
42+
// ...
43+
$commandBus->handle(new FooCommand());
44+
45+
Considering that only one command is handled at a time, instantiating all the
46+
other command handlers is unnecessary. A possible solution to lazy-load the
47+
handlers could be to inject the whole dependency injection container::
48+
49+
use Symfony\Component\DependencyInjection\ContainerInterface;
50+
51+
class CommandBus
52+
{
53+
private $container;
54+
55+
public function __construct(ContainerInterface $container)
56+
{
57+
$this->container = $container;
58+
}
59+
60+
public function handle(Command $command)
61+
{
62+
$commandClass = get_class($command)
63+
64+
if ($this->container->has($commandClass)) {
65+
$handler = $this->container->get($commandClass);
66+
67+
return $handler->handle($command);
68+
}
69+
}
70+
}
71+
72+
However, injecting the entire container is discouraged because it gives too
73+
broad access to existing services and it hides the actual dependencies of the
74+
services.
75+
76+
**Service Locators** are intended to solve this problem by giving access to a
77+
set of predefined services while instantiating them only when actually needed.
78+
79+
Defining a Service Locator
80+
--------------------------
81+
82+
First, define a new service for the service locator. Use its ``arguments``
83+
option to include as many services as needed to it and add the
84+
``container.service_locator`` tag to turn it into a service locator:
85+
86+
.. configuration-block::
87+
88+
.. code-block:: yaml
89+
90+
services:
91+
92+
app.command_handler_locator:
93+
class: Symfony\Component\DependencyInjection\ServiceLocator
94+
tags: ['container.service_locator']
95+
arguments:
96+
-
97+
AppBundle\FooCommand: '@app.command_handler.foo'
98+
AppBundle\BarCommand: '@app.command_handler.bar'
99+
100+
.. code-block:: xml
101+
102+
<?xml version="1.0" encoding="UTF-8" ?>
103+
<container xmlns="http://symfony.com/schema/dic/services"
104+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
105+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
106+
107+
<services>
108+
109+
<service id="app.command_handler_locator" class="Symfony\Component\DependencyInjection\ServiceLocator">
110+
<argument type="collection">
111+
<argument key="AppBundle\FooCommand" type="service" id="app.command_handler.foo" />
112+
<argument key="AppBundle\BarCommand" type="service" id="app.command_handler.bar" />
113+
</argument>
114+
<tag name="container.service_locator" />
115+
</service>
116+
117+
</services>
118+
</container>
119+
120+
.. code-block:: php
121+
122+
use Symfony\Component\DependencyInjection\ServiceLocator;
123+
use Symfony\Component\DependencyInjection\Reference;
124+
125+
//...
126+
127+
$container
128+
->register('app.command_handler_locator', ServiceLocator::class)
129+
->addTag('container.service_locator')
130+
->setArguments(array(array(
131+
'AppBundle\FooCommand' => new Reference('app.command_handler.foo'),
132+
'AppBundle\BarCommand' => new Reference('app.command_handler.bar'),
133+
)))
134+
;
135+
136+
.. note::
137+
138+
The services defined in the service locator argument must include keys,
139+
which later become their unique identifiers inside the locator.
140+
141+
Now you can use the service locator injecting it in any other service:
142+
143+
.. configuration-block::
144+
145+
.. code-block:: yaml
146+
147+
services:
148+
149+
AppBundle\CommandBus:
150+
arguments: ['@app.command_handler_locator']
151+
152+
.. code-block:: xml
153+
154+
<?xml version="1.0" encoding="UTF-8" ?>
155+
<container xmlns="http://symfony.com/schema/dic/services"
156+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
157+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
158+
159+
<services>
160+
161+
<service id="AppBundle\CommandBus">
162+
<argument type="service" id="app.command_handler.locator" />
163+
</service>
164+
165+
</services>
166+
</container>
167+
168+
.. code-block:: php
169+
170+
use AppBundle\CommandBus;
171+
use Symfony\Component\DependencyInjection\Reference;
172+
173+
//...
174+
175+
$container
176+
->register(CommandBus::class)
177+
->setArguments(array(new Reference('app.command_handler_locator')))
178+
;
179+
180+
.. tip::
181+
182+
If the service locator is not intended to be used by multiple services, it's
183+
better to create and inject it as an anonymous service.
184+
185+
Usage
186+
-----
187+
188+
Back to the previous CommandBus example, it looks like this when using the
189+
service locator::
190+
191+
// ...
192+
use Psr\Container\ContainerInterface;
193+
194+
class CommandBus
195+
{
196+
/**
197+
* @var ContainerInterface
198+
*/
199+
private $handlerLocator;
200+
201+
// ...
202+
203+
public function handle(Command $command)
204+
{
205+
$commandClass = get_class($command);
206+
207+
if (!$this->handlerLocator->has($commandClass)) {
208+
return;
209+
}
210+
211+
$handler = $this->handlerLocator->get($commandClass);
212+
213+
return $handler->handle($command);
214+
}
215+
}
216+
217+
The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator`
218+
which implements the PSR-11 ``ContainerInterface``, but it is also a callable::
219+
220+
// ...
221+
$locateHandler = $this->handlerLocator;
222+
$handler = $locateHandler($commandClass);
223+
224+
return $handler->handle($command);
225+
226+
.. _`Command pattern`: https://en.wikipedia.org/wiki/Command_pattern

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.