@@ -4,19 +4,21 @@ single: DependencyInjection; Service Subscribers
4
4
Service Subscribers
5
5
================
6
6
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.
13
15
14
16
Suppose you've got a mailing system with multiple ``Updater `` classes for
15
17
sending out mails on different occasions. Since every updater has the same
16
18
purpose, we start with a base updater::
17
19
18
- // src/Updates/AbstractUpdater.php
19
- namespace App \Updates;
20
+ // src/AppBundle/ Updates/AbstractUpdater.php
21
+ namespace AppBundle \Updates;
20
22
21
23
use Twig\Environment;
22
24
@@ -56,10 +58,10 @@ purpose, we start with a base updater::
56
58
57
59
Now that we have a base class, we can add updaters for different entities. Note
58
60
that we have to inject the dependencies of ``AbstractUpdater `` to each
59
- updater. ::
61
+ updater::
60
62
61
- // src/Updates/FooUpdater.php
62
- namespace App \Updates;
63
+ // src/AppBundle/ Updates/FooUpdater.php
64
+ namespace AppBundle \Updates;
63
65
64
66
use Doctrine\Common\Persistence\ManagerRegistry;
65
67
use Twig\Environment;
@@ -84,9 +86,10 @@ updater.::
84
86
}
85
87
}
86
88
87
- // src/Updates/BarUpdater.php
88
- namespace App \Updates;
89
+ // src/AppBundle/ Updates/BarUpdater.php
90
+ namespace AppBundle \Updates;
89
91
92
+ use AppBundle\BarManager;
90
93
use Doctrine\Common\Persistence\ManagerRegistry;
91
94
use Twig\Environment;
92
95
@@ -95,7 +98,7 @@ updater.::
95
98
private $doctrine;
96
99
private $barManager;
97
100
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)
99
102
{
100
103
parent::__construct($mailer, $twig);
101
104
@@ -112,10 +115,11 @@ updater.::
112
115
}
113
116
}
114
117
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 ``.
119
123
120
124
By creating a service subscriber of the base class, we can create a more
121
125
practical solution for our dependencies.
@@ -126,50 +130,274 @@ Defining a Service Subscriber
126
130
First, turn ``AbstractUpdater `` into an implementation of
127
131
``ServiceSubscriberInterface `` by adding the static method
128
132
``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
+ }
130
184
131
- <code>
132
185
133
186
With this newly created service subscriber, the updaters can be adapted to
134
187
coincide with the service locator::
135
188
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
+ }
137
228
138
229
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.
140
231
141
232
.. configuration-block ::
142
233
143
234
.. code-block :: yaml
144
235
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'
146
253
147
254
.. code-block :: xml
148
255
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 >
150
270
151
271
.. code-block :: php
152
272
153
273
<code >
154
274
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
+
155
326
Usage
156
327
-----
157
328
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'
160
343
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 >
162
358
163
- For mandatory dependencies:
359
+ </services >
360
+ </container >
361
+
362
+ .. code-block :: php
363
+
364
+ <code >
164
365
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::
169
367
170
- otherwise:
368
+ namespace AppBundle;
171
369
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
+ }
175
403
0 commit comments