From 1ffdcee77211e93aa7909835b22a45e555cebd23 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 25 May 2023 15:23:42 +0200 Subject: [PATCH] [DependencyInjection] Add support for generating lazy closures --- reference/attributes.rst | 4 +- service_container.rst | 50 ++----------- service_container/autowiring.rst | 98 ++++++++++++++++++++++++++ service_container/service_closures.rst | 42 ++--------- 4 files changed, 110 insertions(+), 84 deletions(-) diff --git a/reference/attributes.rst b/reference/attributes.rst index 5598691682e..2cabf9de7bd 100644 --- a/reference/attributes.rst +++ b/reference/attributes.rst @@ -31,9 +31,9 @@ Dependency Injection * :ref:`Autoconfigure ` * :ref:`AutoconfigureTag ` * :ref:`Autowire ` -* :ref:`AutowireCallable ` +* :ref:`AutowireCallable ` * :doc:`AutowireDecorated ` -* :doc:`AutowireServiceClosure ` +* :ref:`AutowireServiceClosure ` * :ref:`Exclude ` * :ref:`TaggedIterator ` * :ref:`TaggedLocator ` diff --git a/service_container.rst b/service_container.rst index c87b83d9f9f..0b7e34a947b 100644 --- a/service_container.rst +++ b/service_container.rst @@ -780,54 +780,14 @@ Our configuration looks like this: ; }; -.. versionadded:: 6.1 - - The ``closure`` argument type was introduced in Symfony 6.1. - -It is also possible to convert a callable into an injected closure -thanks to the -:class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable` -attribute. Let's say our ``MessageHashGenerator`` class now has a ``generate()`` -method:: - - // src/Hash/MessageHashGenerator.php - namespace App\Hash; - - class MessageHashGenerator - { - public function generate(): string - { - // Compute and return a message hash - } - } +.. seealso:: -We can inject the ``generate()`` method of the ``MessageHashGenerator`` -like this:: - - // src/Service/MessageGenerator.php - namespace App\Service; - - use App\Hash\MessageHashGenerator; - use Psr\Log\LoggerInterface; - use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; + Closures can be injected :ref:`by using autowiring ` + and its dedicated attributes. - class MessageGenerator - { - public function __construct( - private LoggerInterface $logger, - #[AutowireCallable(service: MessageHashGenerator::class, method: 'generate')] - private \Closure $generateMessageHash - ) { - // ... - } - - // ... - } - -.. versionadded:: 6.3 +.. versionadded:: 6.1 - The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable` - attribute was introduced in Symfony 6.3. + The ``closure`` argument type was introduced in Symfony 6.1. .. _services-binding: diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index 7775935076a..81f877e23fd 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -643,6 +643,104 @@ The ``#[Autowire]`` attribute can also be used for :ref:`parameters messageFormatterResolver)()->format($message); + + // ... + } + } + +.. versionadded:: 6.3 + + The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireServiceClosure` + attribute was introduced in Symfony 6.3. + +It is common that a service accepts a closure with a specific signature. +In this case, you can use the +:class:`Symfony\Component\DependencyInjection\Attribute\\AutowireCallable` attribute +to generate a closure with the same signature as a specific method of a service. When +this closure is called, it will pass all its arguments to the underlying service +function:: + + // src/Service/MessageGenerator.php + namespace App\Service; + + use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; + + class MessageGenerator + { + public function __construct( + #[AutowireCallable(service: 'third_party.remote_message_formatter', method: 'format')] + \Closure $formatCallable + ) { + } + + public function generate(string $message): void + { + $formattedMessage = ($this->formatCallable)($message); + + // ... + } + } + +Finally, you can pass the ``lazy: true`` option to the +:class:`Symfony\Component\DependencyInjection\Attribute\\AutowireCallable` +attribute. By doing so, the callable will automatically be lazy, which means +that the encapsulated service will be instantiated **only** at the +closure's first call. + +.. versionadded:: 6.3 + + The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable` + attribute was introduced in Symfony 6.3. + .. _autowiring-calls: Autowiring other Methods (e.g. Setters and Public Typed Properties) diff --git a/service_container/service_closures.rst b/service_container/service_closures.rst index 28a1d0b0f02..53cf11850b3 100644 --- a/service_container/service_closures.rst +++ b/service_container/service_closures.rst @@ -92,45 +92,13 @@ argument of type ``service_closure``: .. seealso:: - Another way to inject services lazily is via a - :doc:`service locator `. - -Thanks to the -:class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireServiceClosure` -attribute, defining a service wrapped in a closure can directly be done -in the service class, without further configuration:: - - // src/Service/MyService.php - namespace App\Service; - - use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; - use Symfony\Component\Mailer\MailerInterface; - - class MyService - { - public function __construct( - #[AutowireServiceClosure('mailer')] - private \Closure $mailer - ) { - } - - public function doSomething(): void - { - // ... - - $this->getMailer()->send($email); - } + Service closures can be injected :ref:`by using autowiring ` + and its dedicated attributes. - private function getMailer(): MailerInterface - { - return ($this->mailer)(); - } - } - -.. versionadded:: 6.3 +.. seealso:: - The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireServiceClosure` - attribute was introduced in Symfony 6.3. + Another way to inject services lazily is via a + :doc:`service locator `. Using a Service Closure in a Compiler Pass ------------------------------------------