@@ -4,19 +4,21 @@ single: DependencyInjection; Service Subscribers
44Service 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
1416Suppose you've got a mailing system with multiple ``Updater `` classes for
1517sending out mails on different occasions. Since every updater has the same
1618purpose, 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
5759Now that we have a base class, we can add updaters for different entities. Note
5860that 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
120124By creating a service subscriber of the base class, we can create a more
121125practical solution for our dependencies.
@@ -126,50 +130,274 @@ Defining a Service Subscriber
126130First, 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
133186With this newly created service subscriber, the updaters can be adapted to
134187coincide 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
138229Optionally, 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+
155326Usage
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\L og\L oggerInterface') means the objects use the "logger" name
166- internally to fetch a service which must implement Psr\L og\L oggerInterface.
167- * array('Psr\L og\L oggerInterface') is a shortcut for
168- * array('Psr\L og\L oggerInterface' => 'Psr\L og\L oggerInterface')
366+ An instance of this service would look like this::
169367
170- otherwise:
368+ namespace AppBundle;
171369
172- * array('logger' => '?Psr\L og\L oggerInterface') denotes an optional dependency
173- * array('?Psr\L og\L oggerInterface') is a shortcut for
174- * array('Psr\L og\L oggerInterface' => '?Psr\L og\L oggerInterface')
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