@@ -105,6 +105,112 @@ working with optional dependencies. It is also more difficult to use in
105
105
combination with class hierarchies: if a class uses constructor injection
106
106
then extending it and overriding the constructor becomes problematic.
107
107
108
+ Immutable-setter Injection
109
+ --------------------------
110
+
111
+ .. versionadded :: 4.3
112
+
113
+ The ``immutable-setter `` injection was introduced in Symfony 4.3.
114
+
115
+ Another possible injection is to use a method which returns a separate instance
116
+ by cloning the original service, this approach allows you to make a service immutable::
117
+
118
+ // ...
119
+ use Symfony\Component\Mailer\MailerInterface;
120
+
121
+ class NewsletterManager
122
+ {
123
+ private $mailer;
124
+
125
+ /**
126
+ * @required
127
+ * @return static
128
+ */
129
+ public function withMailer(MailerInterface $mailer)
130
+ {
131
+ $new = clone $this;
132
+ $new->mailer = $mailer;
133
+
134
+ return $new;
135
+ }
136
+
137
+ // ...
138
+ }
139
+
140
+ In order to use this type of injection, don't forget to configure it:
141
+
142
+ .. configuration-block ::
143
+
144
+ .. code-block :: yaml
145
+
146
+ # config/services.yaml
147
+ services :
148
+ # ...
149
+
150
+ app.newsletter_manager :
151
+ class : App\Mail\NewsletterManager
152
+ calls :
153
+ - [withMailer, ['@mailer'], true]
154
+
155
+ .. code-block :: xml
156
+
157
+ <!-- config/services.xml -->
158
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
159
+ <container xmlns =" http://symfony.com/schema/dic/services"
160
+ xmlns : xsi =" https://www.w3.org/2001/XMLSchema-instance"
161
+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
162
+ https://symfony.com/schema/dic/services/services-1.0.xsd" >
163
+
164
+ <services >
165
+ <!-- ... -->
166
+
167
+ <service id =" app.newsletter_manager" class =" App\Mail\NewsletterManager" >
168
+ <call method =" withMailer" returns-clone =" true" >
169
+ <argument type =" service" id =" mailer" />
170
+ </call >
171
+ </service >
172
+ </services >
173
+ </container >
174
+
175
+ .. code-block :: php
176
+
177
+ // config/services.php
178
+ use App\Mail\NewsletterManager;
179
+ use Symfony\Component\DependencyInjection\Reference;
180
+
181
+ // ...
182
+ $container->register('app.newsletter_manager', NewsletterManager::class)
183
+ ->addMethodCall('withMailer', [new Reference('mailer')], true);
184
+
185
+ .. note ::
186
+
187
+ If you decide to use autowiring, this type of injection requires
188
+ that you add a ``@return static `` docblock in order for the container
189
+ to be capable of registering the method.
190
+
191
+ This approach is useful if you need to configure your service according to your needs,
192
+ so, here's the advantages of immutable-setters:
193
+
194
+ * Immutable setters works with optional dependencies, this way, if you don't need
195
+ a dependency, the setter don't need to be called.
196
+
197
+ * Like the constructor injection, using immutable setters force the dependency to stay
198
+ the same during the lifetime of a service.
199
+
200
+ * This type of injection works well with traits as the service can be composed,
201
+ this way, adapting the service to your application requirements is easier.
202
+
203
+ * The setter can be called multiple times, this way, adding a dependency to a collection
204
+ becomes easier and allows you to add a variable number of dependencies.
205
+
206
+ The disadvantages are:
207
+
208
+ * As the setter call is optional, a dependency can be null during execution,
209
+ you must check that the dependency is available before calling it.
210
+
211
+ * Unless the service is declared lazy, it is incompatible with services
212
+ that reference each other in what are called circular loops
213
+
108
214
Setter Injection
109
215
----------------
110
216
@@ -180,6 +286,9 @@ This time the advantages are:
180
286
the method adds the dependency to a collection. You can then have a variable
181
287
number of dependencies.
182
288
289
+ * Like the immutable-setter one, this type of injection works well with
290
+ traits and allows you to compose your service.
291
+
183
292
The disadvantages of setter injection are:
184
293
185
294
* The setter can be called more than just at the time of construction so
@@ -261,3 +370,108 @@ to setter injection but with these additional important problems:
261
370
But, it is useful to know that this can be done with the service container,
262
371
especially if you are working with code that is out of your control, such
263
372
as in a third party library, which uses public properties for its dependencies.
373
+
374
+ Immutable Injection
375
+ -------------------
376
+
377
+ Another possible injection in to use a method which return ``static ``,
378
+ this approach allow you to make a service immutable::
379
+
380
+ // ...
381
+
382
+ class NewsletterManager
383
+ {
384
+ private $mailer;
385
+
386
+ /**
387
+ * @required
388
+ * @return static
389
+ */
390
+ public function withMailer(MailerInterface $mailer)
391
+ {
392
+ $new = clone $this;
393
+ $new->mailer = $mailer;
394
+
395
+ return $new;
396
+ }
397
+
398
+ // ...
399
+ }
400
+
401
+ In order to use the full potential of this approach, you can define a third argument
402
+ which allow the container to return the newly created service:
403
+
404
+ .. configuration-block ::
405
+
406
+ .. code-block :: yaml
407
+
408
+ # config/services.yaml
409
+ services :
410
+ # ...
411
+
412
+ app.newsletter_manager :
413
+ class : App\Mail\NewsletterManager
414
+ calls :
415
+ - [withMailer, ['@mailer'], true]
416
+
417
+ .. code-block :: xml
418
+
419
+ <!-- config/services.xml -->
420
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
421
+ <container xmlns =" http://symfony.com/schema/dic/services"
422
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
423
+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
424
+ http://symfony.com/schema/dic/services/services-1.0.xsd" >
425
+
426
+ <services >
427
+ <!-- ... -->
428
+
429
+ <service id =" app.newsletter_manager" class =" App\Mail\NewsletterManager" >
430
+ <call method =" withMailer" use-result =true>
431
+ <argument type =" service" id =" mailer" />
432
+ </call >
433
+ </service >
434
+ </services >
435
+ </container >
436
+
437
+ .. code-block :: php
438
+
439
+ // config/services.php
440
+ use App\Mail\NewsletterManager;
441
+ use Symfony\Component\DependencyInjection\Reference;
442
+
443
+ // ...
444
+ $container->register('app.newsletter_manager', NewsletterManager::class)
445
+ ->addMethodCall('withMailer', [new Reference('mailer')], true);
446
+
447
+ .. note ::
448
+
449
+ This type of injection requires that you add a ``@return static `` docblock in order
450
+ for the container to be capable of registering the method.
451
+
452
+ This approach is useful if you need to configure your service according to your needs,
453
+ so, what are the advantages?
454
+
455
+ * Your service becomes immutable, as the container will return a new object,
456
+ the initial service stays clean and unchanged.
457
+
458
+ * You can easily change the injected service as long as it respect the interface/type
459
+ asked by the initial service.
460
+
461
+ * It allow you to get rid of factory usages which can lead a more complex code
462
+
463
+ * As the dependency is optional, you can easily decide to receive the service
464
+ without using the "with" method.
465
+
466
+ * As the method automatically receive and set the attribute value,
467
+ you couldn't obtain the newly created service without this dependency.
468
+
469
+ The disadvantages are:
470
+
471
+ * As the ``@return static `` docblock is required by the container to
472
+ understand that the method return a new object,
473
+ you can found that adding docblock for a single method isn't adapted or
474
+ link your code to the container.
475
+
476
+ * As this approach force the container to create a new object
477
+ once the method is called, you can found hard to debug and test your code.
0 commit comments