From 1cb0b0aa4b25eda3d574fdc57568be7f60b4ddbb Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Sun, 5 Dec 2021 13:29:19 -0400 Subject: [PATCH 1/3] Clarify and expand the service decoration description --- service_container/service_decoration.rst | 301 +++++++++++++++++++---- 1 file changed, 257 insertions(+), 44 deletions(-) diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index 4c7f2ed0158..f208ce17d5d 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -12,12 +12,12 @@ When overriding an existing definition, the original service is lost: # config/services.yaml services: - App\Mailer: ~ + App\Mailer\Mailer: ~ - # this replaces the old App\Mailer definition with the new one, the + # this replaces the old App\Mailer\Mailer definition with the new one, the # old definition is lost - App\Mailer: - class: App\NewMailer + App\Mailer\Mailer: + class: App\Mailer\NewMailer .. code-block:: xml @@ -28,11 +28,11 @@ When overriding an existing definition, the original service is lost: xsd:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - + - - + @@ -41,15 +41,15 @@ When overriding an existing definition, the original service is lost: // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\Mailer; - use App\NewMailer; + use App\Mailer\Mailer; + use App\Mailer\NewMailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); $services->set(Mailer::class); - // this replaces the old App\Mailer definition with the new one, the + // this replaces the old App\Mailer\Mailer definition with the new one, the // old definition is lost $services->set(Mailer::class, NewMailer::class); }; @@ -57,7 +57,7 @@ When overriding an existing definition, the original service is lost: Most of the time, that's exactly what you want to do. But sometimes, you might want to decorate the old one instead (i.e. apply the `Decorator pattern`_). In this case, the old service should be kept around to be able to reference -it in the new one. This configuration replaces ``App\Mailer`` with a new one, +it in the new one. This configuration replaces ``App\Mailer\Mailer`` with a new one, but keeps a reference of the old one as ``.inner``: .. configuration-block:: @@ -66,12 +66,12 @@ but keeps a reference of the old one as ``.inner``: # config/services.yaml services: - App\Mailer: ~ + App\Mailer\Mailer: ~ - App\DecoratingMailer: - # overrides the App\Mailer service + App\Mailer\DecoratingMailer: + # overrides the App\Mailer\Mailer service # but that service is still available as ".inner" - decorates: App\Mailer + decorates: App\Mailer\Mailer .. code-block:: xml @@ -82,12 +82,12 @@ but keeps a reference of the old one as ``.inner``: xsd:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - + - - @@ -98,8 +98,8 @@ but keeps a reference of the old one as ``.inner``: // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\DecoratingMailer; - use App\Mailer; + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); @@ -107,13 +107,13 @@ but keeps a reference of the old one as ``.inner``: $services->set(Mailer::class); $services->set(DecoratingMailer::class) - // overrides the App\Mailer service + // overrides the App\Mailer\Mailer service // but that service is still available as ".inner" ->decorate(Mailer::class); }; -The ``decorates`` option tells the container that the ``App\DecoratingMailer`` -service replaces the ``App\Mailer`` service. If you're using the +The ``decorates`` option tells the container that the ``App\Mailer\DecoratingMailer`` +service replaces the ``App\Mailer\Mailer`` service. If you're using the :ref:`default services.yaml configuration `, the decorated service is automatically injected when the constructor of the decorating service has one argument type-hinted with the decorated service class. @@ -129,10 +129,10 @@ automatically changed to ``'.inner'``): # config/services.yaml services: - App\Mailer: ~ + App\Mailer\Mailer: ~ - App\DecoratingMailer: - decorates: App\Mailer + App\Mailer\DecoratingMailer: + decorates: App\Mailer\Mailer # pass the old service as an argument arguments: ['@.inner'] @@ -145,10 +145,10 @@ automatically changed to ``'.inner'``): xsd:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - + - @@ -161,8 +161,8 @@ automatically changed to ``'.inner'``): // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\DecoratingMailer; - use App\Mailer; + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); @@ -183,14 +183,14 @@ automatically changed to ``'.inner'``): .. tip:: - The visibility of the decorated ``App\Mailer`` service (which is an alias - for the new service) will still be the same as the original ``App\Mailer`` + The visibility of the decorated ``App\Mailer\Mailer`` service (which is an alias + for the new service) will still be the same as the original ``App\Mailer\Mailer`` visibility. .. note:: The generated inner id is based on the id of the decorator service - (``App\DecoratingMailer`` here), not of the decorated service (``App\Mailer`` + (``App\Mailer\DecoratingMailer`` here), not of the decorated service (``App\Mailer\Mailer`` here). You can control the inner service name via the ``decoration_inner_name`` option: @@ -200,10 +200,10 @@ automatically changed to ``'.inner'``): # config/services.yaml services: - App\DecoratingMailer: + App\Mailer\DecoratingMailer: # ... - decoration_inner_name: App\DecoratingMailer.wooz - arguments: ['@App\DecoratingMailer.wooz'] + decoration_inner_name: App\Mailer\DecoratingMailer.wooz + arguments: ['@App\Mailer\DecoratingMailer.wooz'] .. code-block:: xml @@ -217,12 +217,12 @@ automatically changed to ``'.inner'``): - + @@ -233,8 +233,8 @@ automatically changed to ``'.inner'``): // config/services.php namespace Symfony\Component\DependencyInjection\Loader\Configurator; - use App\DecoratingMailer; - use App\Mailer; + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; return function(ContainerConfigurator $configurator) { $services = $configurator->services(); @@ -246,6 +246,219 @@ automatically changed to ``'.inner'``): ->args([service(DecoratingMailer::class.'.wooz')]); }; +Two Different Ways to Decorate a Service +---------------------------------------- + +A service can be decorated by either making the decorating service: + +- Implement the same interface as the decorated service, or +- Extend the decorated service. + +Implementing The Same Interface As The Decorated Service +-------------------------------------------------------- + +.. tip:: + + This first method is the recommended way to decorate a service. + + However, it only works when the decorated class implements an injectable interface. + +Assume the following for the decorated class:: + + // src/Mailer/Mailer.php + namespace App\Mailer; + + class Mailer implements MailerInterface { + } + +Also assume that your service definitions are configured so that you would type-hint +``App\Mailer\MailerInterface`` instead of ``App\Mailer\Mailer`` to inject the service into other services. +(If not, and you instead must type-hint ``App\Mailer\Mailer``, then skip to "Extend The Decorated Service".) + +In this case, you would inject the decorated mailer service as follows:: + + // src/Services/AcmeService.php + namespace App\Services; + + use App\Mailer\MailerInterface; + + class AcmeService { + public function __construct(MailerInterface $mailer) { + } + } + +Create your decorating class as follows:: + + // src/Mailer/DecoratingMailer.php + namespace App\Mailer; + + class DecoratingMailer implements MailerInterface { + public function __construct(MailerInterface $mailer) { + } + } + +Your decoration configuration will be as follows: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\Mailer\MailerInterface: + class: App\Mailer\Mailer + + App\Mailer\DecoratingMailer: + decorates: App\Mailer\MailerInterface + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; + use App\Mailer\MailerInterface; + + return function(ContainerConfigurator $configurator) { + $services = $configurator->services(); + + $services->set(MailerInterface::class) + ->class(Mailer::class); + + $services->set(DecoratingMailer::class) + ->decorate(MailerInterface::class); + }; + +Extend The Decorated Service +---------------------------- + +.. tip:: + + This second method of the decorating service extending the decorated service is + not strictly speaking "decoration" in accordance with the `Decorator pattern`_, but it does + allow, in Symfony, for your "decorating" class to be automatically injected when the "decorated" class is + type-hinted. In other words, the end result is the same as the first option. It could help you + with decorating services from third-party bundles that don't adhere to interface implementation + best-practices. + + You might also notice that the ".inner" part of the decoration configuration is meaningless with this + decoration option, because the "decorated" service is not injected into the "decorating" service. + +Assume the following:: + + // src/Mailer/Mailer.php + namespace App\Mailer; + + class Mailer { + } + +In this case, you would inject the ``App\Mailer\Mailer`` service as follows into another service:: + + // src/Services/AcmeService.php + namespace App\Services; + + use App\Mailer\Mailer; + + class AcmeService { + public function __construct(Mailer $mailer) { + } + } + +Create your decorating class as follows:: + + // src/Mailer/DecoratingMailer.php + namespace App\Mailer; + + use App\Mailer\Mailer; + + class DecoratingMailer extends Mailer { + public function __construct() { + } + } + +Your decoration configuration will be as follows: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\Mailer\Mailer: ~ + + App\Mailer\DecoratingMailer: + decorates: App\Mailer\Mailer + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use App\Mailer\DecoratingMailer; + use App\Mailer\Mailer; + + return function(ContainerConfigurator $configurator) { + $services = $configurator->services(); + + $services->set(Mailer::class); + + $services->set(DecoratingMailer::class) + ->decorate(Mailer::class); + }; + +.. tip:: + + If the ``App\Mailer\Mailer`` class is marked as ``final``, and it does not implement an injectable + interface, then you will not be able to decorate it, because a final class cannot be extended. + +.. tip:: + + You might need to use aliases in the ``services`` section of the ``code/services.yaml`` file, if + you're expending a service from a third-party bundle that uses XML config files. It depends on how + the bundle registered its services. + +Congratulations! With both options, the ``App\Mailer\DecoratingMailer`` class is automatically injected +instead of the ``App\Mailer\Mailer`` class. + Decoration Priority ------------------- From 117910e591b3b323a0cf524efc1d7828889a4cab Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Sun, 5 Dec 2021 13:45:19 -0400 Subject: [PATCH 2/3] Correct spelling mistake --- service_container/service_decoration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index f208ce17d5d..b98b8eba84a 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -453,7 +453,7 @@ Your decoration configuration will be as follows: .. tip:: You might need to use aliases in the ``services`` section of the ``code/services.yaml`` file, if - you're expending a service from a third-party bundle that uses XML config files. It depends on how + you're extending a service from a third-party bundle that uses XML config files. It depends on how the bundle registered its services. Congratulations! With both options, the ``App\Mailer\DecoratingMailer`` class is automatically injected From 1de0e8692e8daac0762d31f9b87ba980c6051b6b Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Fri, 17 Dec 2021 15:23:02 -0400 Subject: [PATCH 3/3] Cosmetic change to rerun CI checks --- service_container/service_decoration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_container/service_decoration.rst b/service_container/service_decoration.rst index b98b8eba84a..9fe10139582 100644 --- a/service_container/service_decoration.rst +++ b/service_container/service_decoration.rst @@ -454,7 +454,7 @@ Your decoration configuration will be as follows: You might need to use aliases in the ``services`` section of the ``code/services.yaml`` file, if you're extending a service from a third-party bundle that uses XML config files. It depends on how - the bundle registered its services. + the bundle registered its services. Congratulations! With both options, the ``App\Mailer\DecoratingMailer`` class is automatically injected instead of the ``App\Mailer\Mailer`` class.