From a598bc0dc0f38485e7ee236ce0c214c7aa99ad65 Mon Sep 17 00:00:00 2001 From: Tim Goudriaan Date: Fri, 1 Nov 2019 04:47:25 +0100 Subject: [PATCH] feat(DI): static injection --- components/phpunit_bridge.rst | 6 +- routing.rst | 2 - service_container/calls.rst | 76 ++++++++++++++++++ service_container/definitions.rst | 3 + service_container/injection_types.rst | 109 ++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 5 deletions(-) diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index 989dba24549..09fed684b0d 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -621,7 +621,7 @@ toggle a behavior:: public function hello(): string { if (class_exists(DependencyClass::class)) { - return 'The dependency bahavior.'; + return 'The dependency behavior.'; } return 'The default behavior.'; @@ -639,7 +639,7 @@ are installed during tests) would look like:: public function testHello() { $class = new MyClass(); - $result = $class->hello(); // "The dependency bahavior." + $result = $class->hello(); // "The dependency behavior." // ... } @@ -663,7 +663,7 @@ classes, interfaces and/or traits for the code to run:: ClassExistsMock::withMockedClasses([DependencyClass::class => false]); $class = new MyClass(); - $result = $class->hello(); // "The default bahavior." + $result = $class->hello(); // "The default behavior." // ... } diff --git a/routing.rst b/routing.rst index 810b9310203..0a452e2a8ac 100644 --- a/routing.rst +++ b/routing.rst @@ -431,7 +431,6 @@ defined as ``/blog/{slug}``: - .. code-block:: php @@ -525,7 +524,6 @@ the ``{page}`` parameter using the ``requirements`` option: - .. code-block:: php diff --git a/service_container/calls.rst b/service_container/calls.rst index 925544822a1..f8667fa1a6f 100644 --- a/service_container/calls.rst +++ b/service_container/calls.rst @@ -77,3 +77,79 @@ To configure the container to call the ``setLogger`` method, use the ``calls`` k ->call('setLogger', [ref('logger')]); }; + +.. versionadded:: 4.3 + + The ``immutable-setter`` injection was introduced in Symfony 4.3. + +In order to provide immutable services, some classes implement immutable setters. +Such setters return a new instance of the configured class +instead of mutating the object they were called on:: + + namespace App\Service; + + use Psr\Log\LoggerInterface; + + class MessageGenerator + { + private $logger; + + /** + * @return static + */ + public function withLogger(LoggerInterface $logger) + { + $new = clone $this; + $new->logger = $logger; + + return $new; + } + + // ... + } + +Because the method returns a separate cloned instance, configuring such a service means using +the return value of the wither method (``$service = $service->withLogger($logger);``). +The configuration to tell the container it should do so would be like: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\Service\MessageGenerator: + # ... + calls: + - method: withLogger + arguments: + - '@logger' + returns_clone: true + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use App\Service\MessageGenerator; + use Symfony\Component\DependencyInjection\Reference; + + $container->register(MessageGenerator::class) + ->addMethodCall('withLogger', [new Reference('logger')], true); diff --git a/service_container/definitions.rst b/service_container/definitions.rst index 370f0dc7f25..48ebd7b8818 100644 --- a/service_container/definitions.rst +++ b/service_container/definitions.rst @@ -117,6 +117,9 @@ any method calls in the definitions as well:: // configures a new method call $definition->addMethodCall('setLogger', [new Reference('logger')]); + // configures an immutable-setter + $definition->addMethodCall('withLogger', [new Reference('logger')], true); + // replaces all previously configured method calls with the passed array $definition->setMethodCalls($methodCalls); diff --git a/service_container/injection_types.rst b/service_container/injection_types.rst index 897bb922554..fd383ff11ed 100644 --- a/service_container/injection_types.rst +++ b/service_container/injection_types.rst @@ -105,6 +105,112 @@ working with optional dependencies. It is also more difficult to use in combination with class hierarchies: if a class uses constructor injection then extending it and overriding the constructor becomes problematic. +Immutable-setter Injection +-------------------------- + +.. versionadded:: 4.3 + + The ``immutable-setter`` injection was introduced in Symfony 4.3. + +Another possible injection is to use a method which returns a separate instance +by cloning the original service, this approach allows you to make a service immutable:: + + // ... + use Symfony\Component\Mailer\MailerInterface; + + class NewsletterManager + { + private $mailer; + + /** + * @required + * @return static + */ + public function withMailer(MailerInterface $mailer) + { + $new = clone $this; + $new->mailer = $mailer; + + return $new; + } + + // ... + } + +In order to use this type of injection, don't forget to configure it: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + # ... + + app.newsletter_manager: + class: App\Mail\NewsletterManager + calls: + - [withMailer, ['@mailer'], true] + + .. code-block:: xml + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use App\Mail\NewsletterManager; + use Symfony\Component\DependencyInjection\Reference; + + // ... + $container->register('app.newsletter_manager', NewsletterManager::class) + ->addMethodCall('withMailer', [new Reference('mailer')], true); + +.. note:: + + If you decide to use autowiring, this type of injection requires + that you add a ``@return static`` docblock in order for the container + to be capable of registering the method. + +This approach is useful if you need to configure your service according to your needs, +so, here's the advantages of immutable-setters: + +* Immutable setters works with optional dependencies, this way, if you don't need + a dependency, the setter don't need to be called. + +* Like the constructor injection, using immutable setters force the dependency to stay + the same during the lifetime of a service. + +* This type of injection works well with traits as the service can be composed, + this way, adapting the service to your application requirements is easier. + +* The setter can be called multiple times, this way, adding a dependency to a collection + becomes easier and allows you to add a variable number of dependencies. + +The disadvantages are: + +* As the setter call is optional, a dependency can be null during execution, + you must check that the dependency is available before calling it. + +* Unless the service is declared lazy, it is incompatible with services + that reference each other in what are called circular loops. + Setter Injection ---------------- @@ -180,6 +286,9 @@ This time the advantages are: the method adds the dependency to a collection. You can then have a variable number of dependencies. +* Like the immutable-setter one, this type of injection works well with + traits and allows you to compose your service. + The disadvantages of setter injection are: * The setter can be called more than just at the time of construction so