8000 feat(DI): static injection · symfony/symfony-docs@75162f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 75162f8

Browse files
codedmonkeyGuikingone
authored andcommitted
feat(DI): static injection
1 parent 3a54bc3 commit 75162f8

File tree

5 files changed

+297
-5
lines changed

5 files changed

+297
-5
lines changed

components/phpunit_bridge.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ toggle a behavior::
621621
public function hello(): string
622622
{
623623
if (class_exists(DependencyClass::class)) {
624-
return 'The dependency bahavior.';
624+
return 'The dependency behavior.';
625625
}
626626

627627
return 'The default behavior.';
@@ -639,7 +639,7 @@ are installed during tests) would look like::
639639
public function testHello()
640640
{
641641
$class = new MyClass();
642-
$result = $class->hello(); // "The dependency bahavior."
642+
$result = $class->hello(); // "The dependency behavior."
643643

644644
// ...
645645
}
@@ -663,7 +663,7 @@ classes, interfaces and/or traits for the code to run::
663663
ClassExistsMock::withMockedClasses([DependencyClass::class => false]);
664664

665665
$class = new MyClass();
666-
$result = $class->hello(); // "The default bahavior."
666+
$result = $class->hello(); // "The default behavior."
667667

668668
// ...
669669
}

routing.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,6 @@ defined as ``/blog/{slug}``:
431431
432432
<route id="blog_show" path="/blog/{slug}"
433433
controller="App\Controller\BlogController::show"/>
434-
</route>
435434
</routes>
436435
437436
.. code-block:: php
@@ -525,7 +524,6 @@ the ``{page}`` parameter using the ``requirements`` option:
525524
526525
<route id="blog_show" path="/blog/{slug}"
527526
controller="App\Controller\BlogController::show"/>
528-
</route>
529527
</routes>
530528
531529
.. code-block:: php

service_container/calls.rst

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,80 @@ To configure the container to call the ``setLogger`` method, use the ``calls`` k
7777
->call('setLogger', [ref('logger')]);
7878
};
7979
80+
81+
82+
.. versionadded:: 4.3
83+
84+
The ``immutable-setter`` injection was introduced in Symfony 4.3.
85+
86+
In order to provide immutable services, some classes implement immutable setters.
87+
Such setters return a new instance of the configured class
88+
instead of mutating the object they were called on::
89+
90+
namespace App\Service;
91+
92+
use Psr\Log\LoggerInterface;
93+
94+
class MessageGenerator
95+
{
96+
private $logger;
97+
98+
/**
99+
* @return static
100+
*/
101+
public function withLogger(LoggerInterface $logger)
102+
{
103+
$new = clone $this;
104+
$new->logger = $logger;
105+
106+
return $new;
107+
}
108+
109+
// ...
110+
}
111+
112+
Because the method returns a separate cloned instance, configuring such a service means using
113+
the return value of the wither method (``$service = $service->withLogger($logger);``).
114+
The configuration to tell the container it should do so would be like:
115+
116+
.. configuration-block::
117+
118+
.. code-block:: yaml
119+
120+
# config/services.yaml
121+
services:
122+
App\Service\MessageGenerator:
123+
# ...
124+
calls:
125+
- method: withLogger
126+
arguments:
127+
- '@logger'
128+
returns_clone: true
129+
130+
.. code-block:: xml
131+
132+
<!-- config/services.xml -->
133+
<?xml version="1.0" encoding="UTF-8" ?>
134+
<container xmlns="http://symfony.com/schema/dic/services"
135+
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
136+
xsi:schemaLocation="http://symfony.com/schema/dic/services
137+
https://symfony.com/schema/dic/services/services-1.0.xsd">
138+
139+
<services>
140+
<service id="App\Service\MessageGenerator">
141+
<!-- ... -->
142+
<call method="withLogger" returns-clone="true">
143+
<argument type="service" id="logger" />
144+
</call>
145+
</service>
146+
</services>
147+
</container>
148+
149+
.. code-block:: php
150+
151+
// config/services.php
152+
use App\Service\MessageGenerator;
153+
use Symfony\Component\DependencyInjection\Reference;
154+
155+
$container->register(MessageGenerator::class)
156+
->addMethodCall('withLogger', [new Reference('logger')], true);

service_container/definitions.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ any method calls in the definitions as well::
117117
// configures a new method call
118118
$definition->addMethodCall('setLogger', [new Reference('logger')]);
119119

120+
// configures an immutable-setter
121+
$definition->addMethodCall('withLogger', [new Reference('logger')], true);
122+
120123
// replaces all previously configured method calls with the passed array
121124
$definition->setMethodCalls($methodCalls);
122125

service_container/injection_types.rst

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,112 @@ working with optional dependencies. It is also more difficult to use in
105105
combination with class hierarchies: if a class uses constructor injection
106106
then 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+
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+
108214
Setter 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+
183292
The 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:
261370
But, it is useful to know that this can be done with the service container,
262371
especially if you are working with code that is out of your control, such
263372
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

Comments
 (0)
0