Skip to content

Commit d33c53e

Browse files
committed
Add Usage and Subscribed services sections to Service subscribers
1 parent 6c60e00 commit d33c53e

File tree

1 file changed

+264
-36
lines changed

1 file changed

+264
-36
lines changed

service_container/service_subscribers.rst

Lines changed: 264 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ single: DependencyInjection; Service Subscribers
44
Service Subscribers
55
================
66

7-
Symfony's Service Locators provide a powerful way of passing a subset of
8-
services from the service container to a service. Sometimes, you may want a
9-
more concrete way of defining the services contained within a service locator.
10-
By implementing ``ServiceSubscriberInterface`` you can specify the contents of
11-
the service locator from the object itself. This is useful when a set of
12-
services have the same parent class and similar dependencies.
7+
Symfony's :doc:`Service Locators </service_container/service_locators>` provide
8+
a powerful way of passing a subset of services from the service container to a
9+
service. Sometimes, you may want a more concrete way of defining the services
10+
contained within a service locator. By implementing
11+
:class:`Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface`
12+
you can specify the contents of the service locator from the object itself.
13+
This is useful when a set of services have the same parent class and similar
14+
dependencies.
1315

1416
Suppose you've got a mailing system with multiple ``Updater`` classes for
1517
sending out mails on different occasions. Since every updater has the same
1618
purpose, we start with a base updater::
1719

18-
// src/Updates/AbstractUpdater.php
19-
namespace App\Updates;
20+
// src/AppBundle/Updates/AbstractUpdater.php
21+
namespace AppBundle\Updates;
2022

2123
use Twig\Environment;
2224

@@ -56,10 +58,10 @@ purpose, we start with a base updater::
5658

5759
Now that we have a base class, we can add updaters for different entities. Note
5860
that we have to inject the dependencies of ``AbstractUpdater`` to each
59-
updater.::
61+
updater::
6062

61-
// src/Updates/FooUpdater.php
62-
namespace App\Updates;
63+
// src/AppBundle/Updates/FooUpdater.php
64+
namespace AppBundle\Updates;
6365

6466
use Doctrine\Common\Persistence\ManagerRegistry;
6567
use Twig\Environment;
@@ -84,9 +86,10 @@ updater.::
8486
}
8587
}
8688

87-
// src/Updates/BarUpdater.php
88-
namespace App\Updates;
89+
// src/AppBundle/Updates/BarUpdater.php
90+
namespace AppBundle\Updates;
8991

92+
use AppBundle\BarManager;
9093
use Doctrine\Common\Persistence\ManagerRegistry;
9194
use Twig\Environment;
9295

@@ -95,7 +98,7 @@ updater.::
9598
private $doctrine;
9699
private $barManager;
97100

98-
public function __construct(\Swift_Mailer $mailer, Environment $twig, ManagerRegistry $doctrine, $barManager = null)
101+
public function __construct(\Swift_Mailer $mailer, Environment $twig, ManagerRegistry $doctrine, BarManager $barManager)
99102
{
100103
parent::__construct($mailer, $twig);
101104

@@ -112,10 +115,11 @@ updater.::
112115
}
113116
}
114117

115-
If you're using the `default services.yaml configuration`_, no additional
116-
configuration is required for these services thanks to autowiring, but
117-
maintaining the list of dependencies through constructor injection will quickly
118-
become cumbersome, especially if you add a dependency in ``AbstractUpdater``.
118+
If you're using the :ref:`default services.yml configuration <service-container-services-load-example>`,
119+
no additional configuration is required for these services thanks to autowiring,
120+
but maintaining the list of dependencies through constructor injection will
121+
quickly become cumbersome, especially if you add a dependency in
122+
``AbstractUpdater``.
119123

120124
By creating a service subscriber of the base class, we can create a more
121125
practical solution for our dependencies.
@@ -126,50 +130,274 @@ Defining a Service Subscriber
126130
First, turn ``AbstractUpdater`` into an implementation of
127131
``ServiceSubscriberInterface`` by adding the static method
128132
``getSubscribedServices`` which maintains a list of subscribed services and
129-
replacing our dependencies with a service locator.::
133+
replacing our dependencies with a service locator::
134+
135+
// src/AppBundle/Updates/AbstractUpdater.php
136+
namespace AppBundle\Updates;
137+
138+
use Doctrine\Common\Persistence\ManagerRegistry;
139+
use Psr\Container\ContainerInterface;
140+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
141+
use Twig\Environment;
142+
143+
abstract class AbstractUpdater implements ServiceSubscriberInterface
144+
{
145+
protected $locator;
146+
147+
public function __construct(ContainerInterface $locator)
148+
{
149+
$this->locator = $locator;
150+
}
151+
152+
public static function getSubscribedServices()
153+
{
154+
return [
155+
'doctrine' => '?'.ManagerRegistry::class,
156+
'mailer' => \Swift_Mailer::class,
157+
'twig' => Environment::class
158+
];
159+
}
160+
161+
162+
abstract public function update($entity);
163+
164+
/**
165+
* Renders a view with Twig
166+
*/
167+
public function render($template, $parameters)
168+
{
169+
return $this->locator->get('twig')->render($template, $parameters);
170+
}
171+
172+
/**
173+
* Sends an email with Swiftmailer
174+
*/
175+
public function send($recipient, $title, $body)
176+
{
177+
$message = new \Swift_Message();
178+
179+
// ...
180+
181+
$this->locator->get('mailer')->send($message);
182+
}
183+
}
130184

131-
<code>
132185

133186
With this newly created service subscriber, the updaters can be adapted to
134187
coincide with the service locator::
135188

136-
<code>
189+
// src/AppBundle/Updates/FooUpdater.php
190+
namespace AppBundle\Updates;
191+
192+
class FooUpdater extends AbstractUpdater
193+
{
194+
public function update($entity)
195+
{
196+
// ...
197+
198+
$body = $this->render('Emails/foo_update.html.twig', [/* ... */]);
199+
$this->send($entity->getRecipient(), 'Foo update', $body);
200+
}
201+
}
202+
203+
// src/AppBundle/Updates/BarUpdater.php
204+
namespace AppBundle\Updates;
205+
206+
use AppBundle\BarManager;
207+
use Psr\Container\ContainerInterface;
208+
209+
class BarUpdater extends AbstractUpdater
210+
{
211+
private $barManager;
212+
213+
public function __construct(ContainerInterface $locator, BarManager $barManager)
214+
{
215+
parent::__construct($locator);
216+
217+
$this->barManager = $barManager;
218+
}
219+
220+
public function update($entity)
221+
{
222+
// ...
223+
224+
$body = $this->render('Emails/bar_update.html.twig', [/* ... */]);
225+
$this->send($entity->getRecipient(), 'Bar update', $body);
226+
}
227+
}
137228

138229
Optionally, you'll need to add the ``container.service_subscriber`` tag to
139-
configure the services to be recognized as service subscribers.
230+
the services definitions of the updaters to enable the service subscribers.
140231

141232
.. configuration-block::
142233

143234
.. code-block:: yaml
144235
145-
<code>
236+
// app/config/services.yml
237+
services:
238+
AppBundle\Updates\:
239+
resource: '../src/Updates'
240+
tags: ['container.service_subscriber']
241+
242+
// or
243+
244+
services:
245+
AppBundle\Updates\FooUpdater:
246+
tags: ['container.service_subscriber']
247+
248+
AppBundle\Updates\BarUpdater:
249+
tags: ['container.service_subscriber']
250+
arguments:
251+
- '@Psr\Container\ContainerInterface'
252+
- '@AppBundle\BarManager'
146253
147254
.. code-block:: xml
148255
149-
<code>
256+
<!-- app/config/services.xml -->
257+
<?xml version="1.0" encoding="UTF-8" ?>
258+
<container xmlns="http://symfony.com/schema/dic/services"
259+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
260+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
261+
262+
<services>
263+
264+
<prototype namespace="AppBundle\Updates\" resource="../src/Updates">
265+
<tag name="container.service_subscriber" />
266+
</prototype>
267+
268+
</services>
269+
</container>
150270
151271
.. code-block:: php
152272
153273
<code>
154274
275+
Subscribed services
276+
-------------------
277+
278+
Service subscribers rely on the ``getSubscribedServices()`` method to return a
279+
list of services types required to build a service locator for instances of
280+
that service subscriber::
281+
282+
use Psr\Log\LoggerInterface;
283+
284+
public static function getSubscribedServices()
285+
{
286+
return [
287+
'AppBundle\FooInterface',
288+
LoggerInterface::class
289+
];
290+
}
291+
292+
Service types can optionally be keyed by the service names used internally by
293+
the service subscriber::
294+
295+
use Psr\Log\LoggerInterface;
296+
297+
public static function getSubscribedServices()
298+
{
299+
return [
300+
'foo' => 'AppBundle\FooInterface',
301+
'logger' => LoggerInterface::class
302+
];
303+
}
304+
305+
Optional dependencies
306+
~~~~~~~~~~~~~~~~~~~~~
307+
308+
For optional dependencies, prepend the service type with a ``?`` to prevent
309+
errors if the service type has not been implemented in the service container::
310+
311+
use Psr\Log\LoggerInterface;
312+
313+
public static function getSubscribedServices()
314+
{
315+
return [
316+
'?AppBundle\FooInterface',
317+
'logger' => '?'.LoggerInterface::class
318+
];
319+
}
320+
321+
.. note::
322+
323+
Make sure an optional service exists by calling ``has()`` on the service
324+
locator before calling the service itself.
325+
155326
Usage
156327
-----
157328

158-
Subscribed services
159-
-------------------
329+
Add the ``container.service_subscriber`` tag to a service definition to turn it
330+
into a service subscriber and add a ``Psr\Container\ContainerInterface``
331+
argument to pass the generated service locator to the service instance.
332+
333+
.. configuration-block::
334+
335+
.. code-block:: yaml
336+
337+
// app/config/services.yml
338+
services:
339+
AppBundle\FooHandler:
340+
tags: ['container.service_subscriber']
341+
arguments:
342+
- '@Psr\Container\ContainerInterface'
160343
161-
Returns an array of service types required by such instances, optionally keyed by the service names used internally.
344+
.. code-block:: xml
345+
346+
<!-- app/config/services.xml -->
347+
<?xml version="1.0" encoding="UTF-8" ?>
348+
<container xmlns="http://symfony.com/schema/dic/services"
349+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
350+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
351+
352+
<services>
353+
354+
<service id="app.foo_handler" class="AppBundle\FooHandler">
355+
<argument type="service" id="Psr\Container\ContainerInterface" />
356+
<tag name="container.service_subscriber" />
357+
</service>
162358
163-
For mandatory dependencies:
359+
</services>
360+
</container>
361+
362+
.. code-block:: php
363+
364+
<code>
164365
165-
* array('logger' => 'Psr\Log\LoggerInterface') means the objects use the "logger" name
166-
internally to fetch a service which must implement Psr\Log\LoggerInterface.
167-
* array('Psr\Log\LoggerInterface') is a shortcut for
168-
* array('Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface')
366+
An instance of this service would look like this::
169367

170-
otherwise:
368+
namespace AppBundle;
171369

172-
* array('logger' => '?Psr\Log\LoggerInterface') denotes an optional dependency
173-
* array('?Psr\Log\LoggerInterface') is a shortcut for
174-
* array('Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface')
370+
use Psr\Container\ContainerInterface;
371+
use Psr\Log\LoggerInterface;
372+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
373+
374+
class FooHandler implements ServiceSubscriberInterface
375+
{
376+
/**
377+
* @var ContainerInterface
378+
*/
379+
private $locator;
380+
381+
public function __construct(ContainerInterface $locator)
382+
{
383+
$this->locator = $locator;
384+
}
385+
386+
public static function getSubscribedServices()
387+
{
388+
return [
389+
'AppBundle\FooInterface',
390+
'logger' => '?'.LoggerInterface::class
391+
];
392+
}
393+
394+
public function handle()
395+
{
396+
$this->locator->get('AppBundle\FooInterface')->execute();
397+
398+
if ($this->locator->has('logger')) {
399+
$this->locator->get('logger')->info('Executed foo.');
400+
}
401+
}
402+
}
175403

0 commit comments

Comments
 (0)