@@ -105,6 +105,112 @@ working with optional dependencies. It is also more difficult to use in
105105combination with class hierarchies: if a class uses constructor injection
106106then extending it and overriding the constructor becomes problematic.
107107
108+ Immutable-setter Injection
109+ --------------------------
110+
111+ .. versionadded :: 4.3
112+
113+ The ``immutable-setter `` injection was introduced in Symfony 4.3.
114+
115
8090
+ 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+
E875
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+
108214Setter Injection
109215----------------
110216
@@ -180,6 +286,9 @@ This time the advantages are:
180286 the method adds the dependency to a collection. You can then have a variable
181287 number of dependencies.
182288
289+ * Like the immutable-setter one, this type of injection works well with
290+ traits and allows you to compose your service.
291+
183292The disadvantages of setter injection are:
184293
185294* 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:
261370But, it is useful to know that this can be done with the service container,
262371especially if you are working with code that is out of your control, such
263372as 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