Skip to content

Commit 5a80eea

Browse files
committed
Refactor service locators article to primarily describe service subscribers
1 parent 33550f5 commit 5a80eea

File tree

2 files changed

+171
-50
lines changed

2 files changed

+171
-50
lines changed

service_container/lazy_services.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Lazy Services
66

77
.. seealso::
88

9-
Another way to inject services lazily is via a :doc:`service locator </service_container/service_locators>`.
9+
Another way to inject services lazily is via a :doc:`service subscriber </service_container/service_subscribers>`.
1010

1111
Why Lazy Services?
1212
------------------

service_container/service_locators.rst renamed to service_container/service_subscribers.rst

Lines changed: 170 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
.. index::
2-
single: DependencyInjection; Service Locators
2+
single: DependencyInjection; Service Subscribers
33

4-
Service Locators
5-
================
4+
Service Subscribers
5+
===================
66

77
Sometimes, a service needs access to several other services without being sure
88
that all of them will actually be used. In those cases, you may want the
@@ -77,15 +77,177 @@ However, injecting the entire container is discouraged because it gives too
7777
broad access to existing services and it hides the actual dependencies of the
7878
services.
7979

80-
**Service Locators** are intended to solve this problem by giving access to a
81-
set of predefined services while instantiating them only when actually needed.
80+
**Service Subscribers** are intended to solve this problem by giving access to a
81+
set of predefined services while instantiating them only when actually needed
82+
through a service locator.
83+
84+
Defining a Service Subscriber
85+
-----------------------------
86+
87+
First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface`.
88+
Use its ``getSubscribedServices`` method to include as many services as needed
89+
in the service locater and change the container to a PSR-11 ``ContainerInterface``::
90+
91+
// src/AppBundle/CommandBus.php
92+
namespace AppBundle;
93+
94+
use AppBundle\CommandHandler\BarHandler;
95+
use AppBundle\CommandHandler\FooHandler;
96+
use Psr\Container\ContainerInterface;
97+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
98+
99+
class CommandBus implements ServiceSubscriberInterface
100+
{
101+
private $locator;
102+
103+
public function __construct(ContainerInterface $locator)
104+
{
105+
$this->locator = $locator;
106+
}
107+
108+
public static function getSubscribedServices()
109+
{
110+
return [
111+
'AppBundle\FooCommand' => FooHandler::class,
112+
'AppBundle\BarCommand' => BarHandler::class
113+
];
114+
}
115+
116+
public function handle(Command $command)
117+
{
118+
$commandClass = get_class($command);
119+
120+
if ($this->locator->has($commandClass)) {
121+
$handler = $this->locator->get($commandClass);
122+
123+
return $handler->handle($command);
124+
}
125+
}
126+
}
127+
128+
.. tip::
129+
130+
If the container does *not* contain the subscribed services, double-check
131+
that you have :ref:`autoconfigure <services-autoconfigure>` enabled. You
132+
can also manually add the ``container.service_subscriber`` tag.
133+
134+
The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator`
135+
which implements the PSR-11 ``ContainerInterface``, but it is also a callable::
136+
137+
// ...
138+
$locateHandler = $this->locator;
139+
$handler = $locateHandler($commandClass);
140+
141+
return $handler->handle($command);
142+
143+
Including services
144+
------------------
145+
146+
In order to add a new dependency to the service subscriber, use the
147+
``getSubscribedServices()`` method to add service types to include in the
148+
service locator::
149+
150+
use Psr\Log\LoggerInterface;
151+
152+
public static function getSubscribedServices()
153+
{
154+
return [
155+
//...
156+
LoggerInterface::class
157+
];
158+
}
159+
160+
Service types can also be keyed by a service name for use internally::
161+
162+
use Psr\Log\LoggerInterface;
163+
164+
public static function getSubscribedServices()
165+
{
166+
return [
167+
//...
168+
'logger' => LoggerInterface::class
169+
];
170+
}
171+
172+
Optional services
173+
~~~~~~~~~~~~~~~~~
174+
175+
For optional dependencies, prepend the service type with a ``?`` to prevent
176+
errors if there's no matching service in the service container::
177+
178+
use Psr\Log\LoggerInterface;
179+
180+
public static function getSubscribedServices()
181+
{
182+
return [
183+
//...
184+
'?'.LoggerInterface::class
185+
];
186+
}
187+
188+
.. note::
189+
190+
Make sure an optional service exists by calling ``has()`` on the service
191+
locator before calling the service itself.
192+
193+
Aliased services
194+
~~~~~~~~~~~~~~~~
195+
196+
By default, autowiring is used to match a service type to a service from the
197+
service container. If you don't use autowiring or need to add a non-traditional
198+
service as a dependency, use the ``container.service_subscriber`` tag to map a
199+
service type to a service.
200+
201+
.. configuration-block::
202+
203+
.. code-block:: yaml
204+
205+
// app/config/services.yml
206+
services:
207+
AppBundle\CommandBus:
208+
tags:
209+
- { name: 'container.service_subscriber', key: 'logger', id: 'monolog.logger.event' }
210+
211+
.. code-block:: xml
212+
213+
<!-- app/config/services.xml -->
214+
<?xml version="1.0" encoding="UTF-8" ?>
215+
<container xmlns="http://symfony.com/schema/dic/services"
216+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
217+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
218+
219+
<services>
220+
221+
<service id="AppBundle\CommandBus">
222+
<tag name="container.service_subscriber" key="logger" id="monolog.logger.event" />
223+
</service>
224+
225+
</services>
226+
</container>
227+
228+
.. code-block:: php
229+
230+
// app/config/services.php
231+
use AppBundle\CommandBus;
232+
233+
//...
234+
235+
$container
236+
->register(CommandBus::class)
237+
->addTag('container.service_subscriber', array('key' => 'logger', 'id' => 'monolog.logger.event'))
238+
;
239+
240+
.. tip::
241+
242+
The ``key`` attribute can be omitted if the service name internally is the
243+
same as in the service container.
82244

83245
Defining a Service Locator
84246
--------------------------
85247

86-
First, define a new service for the service locator. Use its ``arguments``
87-
option to include as many services as needed to it and add the
88-
``container.service_locator`` tag to turn it into a service locator:
248+
To manually define a service locator, create a new service definition and add
249+
the ``container.service_locator`` tag to it. Use its ``arguments`` option to
250+
include as many services as needed in it.
89251

90252
.. configuration-block::
91253

@@ -188,45 +350,4 @@ Now you can use the service locator injecting it in any other service:
188350
If the service locator is not intended to be used by multiple services, it's
189351
better to create and inject it as an anonymous service.
190352

191-
Usage
192-
-----
193-
194-
Back to the previous ``CommandBus`` example, it looks like this when using the
195-
service locator::
196-
197-
// ...
198-
use Psr\Container\ContainerInterface;
199-
200-
class CommandBus
201-
{
202-
/**
203-
* @var ContainerInterface
204-
*/
205-
private $handlerLocator;
206-
207-
// ...
208-
209-
public function handle(Command $command)
210-
{
211-
$commandClass = get_class($command);
212-
213-
if (!$this->handlerLocator->has($commandClass)) {
214-
return;
215-
}
216-
217-
$handler = $this->handlerLocator->get($commandClass);
218-
219-
return $handler->handle($command);
220-
}
221-
}
222-
223-
The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator`
224-
which implements the PSR-11 ``ContainerInterface``, but it is also a callable::
225-
226-
// ...
227-
$locateHandler = $this->handlerLocator;
228-
$handler = $locateHandler($commandClass);
229-
230-
return $handler->handle($command);
231-
232353
.. _`Command pattern`: https://en.wikipedia.org/wiki/Command_pattern

0 commit comments

Comments
 (0)