From 6957aee2b1ddaaccef25e4e4266eaa693cf2169e Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 25 May 2023 17:19:58 +0200 Subject: [PATCH] [DependencyInjection] Add support for casting callables into single-method interfaces --- service_container.rst | 124 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/service_container.rst b/service_container.rst index c87b83d9f9f..b7a9ace7f84 100644 --- a/service_container.rst +++ b/service_container.rst @@ -1334,6 +1334,130 @@ the closure:: // services depending on which environment you're on }; +Generating Adapters for Functional Interfaces +--------------------------------------------- + +Functional interfaces are interfaces with a single method. +They are conceptually very similar to a closure except that their only method +has a name. Moreover, they can be used as type-hints across your code. + +The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable` +attribute can be used to generate an adapter for a functional interface. +Let's say you have the following functional interface:: + + // src/Service/MessageFormatterInterface.php + namespace App\Service; + + interface MessageFormatterInterface + { + public function format(string $message, array $parameters): string; + } + +Now, you can define a service implementing this method, among other util ones:: + + // src/Service/MessageFormatterInterface.php + namespace App\Service; + + class MessageUtils + { + // other utils methods... + + public function format($string $message, array $parameters): string + { + // ... + } + } + +We can now use ``#[AutowireCallable]`` with our ``MessageUtils`` service +to inject our functional interface implementation:: + + namespace App\Service\Mail; + + use App\Service\MessageFormatterInterface; + use App\Service\MessageUtils; + use Symfony\Component\DependencyInjection\Attribute\AutowireCallable; + + class Mailer + { + public function __construct( + #[AutowireCallable(service: MessageUtils::class, method: 'formatMessage')] + private MessageFormatterInterface $formatter + ) { + } + + public function sendMail($string $message, array $parameters): string + { + $formattedMessage = $this->formatter->format($message, $parameters); + + // ... + } + } + +.. versionadded:: 6.3 + + The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\AutowireCallable` + attribute was introduced in Symfony 6.3. + +Alternatively, generating an adapter for a functional interface can also +be done through configuration: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + + # ... + + app.message_formatter: + class: App\Service\MessageFormatterInterface + from_callable: [!service {class: 'App\Service\MessageUtils'}, 'formatMessage'] + + .. code-block:: xml + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Service\MessageFormatterInterface; + use App\Service\MessageUtils; + + return function(ContainerConfigurator $container) { + // ... + + $container + ->set('app.message_formatter', MessageFormatterInterface::class) + ->fromCallable([inline_service(MessageUtils::class), 'formatMessage']) + ->alias(MessageFormatterInterface::class, 'app.message_formatter') + ; + }; + +By doing so, Symfony will generate a class (also called an *adapter*) +implementing ``MessageFormatterInterface`` that will forward calls of +``MessageFormatterInterface::format()`` to your underlying service's method +``MessageUtils::format()``, with all its arguments. + Learn more ----------