From 5a80eeaf168a484c2e9a44639d34f43f112d5316 Mon Sep 17 00:00:00 2001 From: Tim Goudriaan Date: Fri, 16 Feb 2018 19:48:05 +0100 Subject: [PATCH 1/3] Refactor service locators article to primarily describe service subscribers --- service_container/lazy_services.rst | 2 +- ...e_locators.rst => service_subscribers.rst} | 219 ++++++++++++++---- 2 files changed, 171 insertions(+), 50 deletions(-) rename service_container/{service_locators.rst => service_subscribers.rst} (58%) diff --git a/service_container/lazy_services.rst b/service_container/lazy_services.rst index 2b0ab1ee960..e698ceda679 100644 --- a/service_container/lazy_services.rst +++ b/service_container/lazy_services.rst @@ -6,7 +6,7 @@ Lazy Services .. seealso:: - Another way to inject services lazily is via a :doc:`service locator `. + Another way to inject services lazily is via a :doc:`service subscriber `. Why Lazy Services? ------------------ diff --git a/service_container/service_locators.rst b/service_container/service_subscribers.rst similarity index 58% rename from service_container/service_locators.rst rename to service_container/service_subscribers.rst index 5471eef80a5..cc59e4459e9 100644 --- a/service_container/service_locators.rst +++ b/service_container/service_subscribers.rst @@ -1,8 +1,8 @@ .. index:: - single: DependencyInjection; Service Locators + single: DependencyInjection; Service Subscribers -Service Locators -================ +Service Subscribers +=================== Sometimes, a service needs access to several other services without being sure that all of them will actually be used. In those cases, you may want the @@ -77,15 +77,177 @@ However, injecting the entire container is discouraged because it gives too broad access to existing services and it hides the actual dependencies of the services. -**Service Locators** are intended to solve this problem by giving access to a -set of predefined services while instantiating them only when actually needed. +**Service Subscribers** are intended to solve this problem by giving access to a +set of predefined services while instantiating them only when actually needed +through a service locator. + +Defining a Service Subscriber +----------------------------- + +First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface`. +Use its ``getSubscribedServices`` method to include as many services as needed +in the service locater and change the container to a PSR-11 ``ContainerInterface``:: + + // src/AppBundle/CommandBus.php + namespace AppBundle; + + use AppBundle\CommandHandler\BarHandler; + use AppBundle\CommandHandler\FooHandler; + use Psr\Container\ContainerInterface; + use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; + + class CommandBus implements ServiceSubscriberInterface + { + private $locator; + + public function __construct(ContainerInterface $locator) + { + $this->locator = $locator; + } + + public static function getSubscribedServices() + { + return [ + 'AppBundle\FooCommand' => FooHandler::class, + 'AppBundle\BarCommand' => BarHandler::class + ]; + } + + public function handle(Command $command) + { + $commandClass = get_class($command); + + if ($this->locator->has($commandClass)) { + $handler = $this->locator->get($commandClass); + + return $handler->handle($command); + } + } + } + +.. tip:: + + If the container does *not* contain the subscribed services, double-check + that you have :ref:`autoconfigure ` enabled. You + can also manually add the ``container.service_subscriber`` tag. + +The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator` +which implements the PSR-11 ``ContainerInterface``, but it is also a callable:: + + // ... + $locateHandler = $this->locator; + $handler = $locateHandler($commandClass); + + return $handler->handle($command); + +Including services +------------------ + +In order to add a new dependency to the service subscriber, use the +``getSubscribedServices()`` method to add service types to include in the +service locator:: + + use Psr\Log\LoggerInterface; + + public static function getSubscribedServices() + { + return [ + //... + LoggerInterface::class + ]; + } + +Service types can also be keyed by a service name for use internally:: + + use Psr\Log\LoggerInterface; + + public static function getSubscribedServices() + { + return [ + //... + 'logger' => LoggerInterface::class + ]; + } + +Optional services +~~~~~~~~~~~~~~~~~ + +For optional dependencies, prepend the service type with a ``?`` to prevent +errors if there's no matching service in the service container:: + + use Psr\Log\LoggerInterface; + + public static function getSubscribedServices() + { + return [ + //... + '?'.LoggerInterface::class + ]; + } + +.. note:: + + Make sure an optional service exists by calling ``has()`` on the service + locator before calling the service itself. + +Aliased services +~~~~~~~~~~~~~~~~ + +By default, autowiring is used to match a service type to a service from the +service container. If you don't use autowiring or need to add a non-traditional +service as a dependency, use the ``container.service_subscriber`` tag to map a +service type to a service. + +.. configuration-block:: + + .. code-block:: yaml + + // app/config/services.yml + services: + AppBundle\CommandBus: + tags: + - { name: 'container.service_subscriber', key: 'logger', id: 'monolog.logger.event' } + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // app/config/services.php + use AppBundle\CommandBus; + + //... + + $container + ->register(CommandBus::class) + ->addTag('container.service_subscriber', array('key' => 'logger', 'id' => 'monolog.logger.event')) + ; + +.. tip:: + + The ``key`` attribute can be omitted if the service name internally is the + same as in the service container. Defining a Service Locator -------------------------- -First, define a new service for the service locator. Use its ``arguments`` -option to include as many services as needed to it and add the -``container.service_locator`` tag to turn it into a service locator: +To manually define a service locator, create a new service definition and add +the ``container.service_locator`` tag to it. Use its ``arguments`` option to +include as many services as needed in it. .. configuration-block:: @@ -188,45 +350,4 @@ Now you can use the service locator injecting it in any other service: If the service locator is not intended to be used by multiple services, it's better to create and inject it as an anonymous service. -Usage ------ - -Back to the previous ``CommandBus`` example, it looks like this when using the -service locator:: - - // ... - use Psr\Container\ContainerInterface; - - class CommandBus - { - /** - * @var ContainerInterface - */ - private $handlerLocator; - - // ... - - public function handle(Command $command) - { - $commandClass = get_class($command); - - if (!$this->handlerLocator->has($commandClass)) { - return; - } - - $handler = $this->handlerLocator->get($commandClass); - - return $handler->handle($command); - } - } - -The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator` -which implements the PSR-11 ``ContainerInterface``, but it is also a callable:: - - // ... - $locateHandler = $this->handlerLocator; - $handler = $locateHandler($commandClass); - - return $handler->handle($command); - .. _`Command pattern`: https://en.wikipedia.org/wiki/Command_pattern From 1754ff83bace12c9887b6204f04fe4203b20ad3a Mon Sep 17 00:00:00 2001 From: Tim Goudriaan Date: Wed, 28 Feb 2018 16:50:50 +0100 Subject: [PATCH 2/3] Minor tweaks to service subscribers article --- service_container/service_subscribers.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/service_container/service_subscribers.rst b/service_container/service_subscribers.rst index cc59e4459e9..d38cb883a25 100644 --- a/service_container/service_subscribers.rst +++ b/service_container/service_subscribers.rst @@ -79,14 +79,15 @@ services. **Service Subscribers** are intended to solve this problem by giving access to a set of predefined services while instantiating them only when actually needed -through a service locator. +through a **Service Locator**, a separate lazy-loaded container. Defining a Service Subscriber ----------------------------- First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface`. Use its ``getSubscribedServices`` method to include as many services as needed -in the service locater and change the container to a PSR-11 ``ContainerInterface``:: +in the service subscriber and change the type hint of the container to +a PSR-11 ``ContainerInterface``:: // src/AppBundle/CommandBus.php namespace AppBundle; @@ -128,7 +129,7 @@ in the service locater and change the container to a PSR-11 ``ContainerInterface .. tip:: If the container does *not* contain the subscribed services, double-check - that you have :ref:`autoconfigure ` enabled. You + that you have :ref:`autoconfigure ` enabled. You can also manually add the ``container.service_subscriber`` tag. The injected service is an instance of :class:`Symfony\\Component\\DependencyInjection\\ServiceLocator` @@ -173,7 +174,7 @@ Optional services ~~~~~~~~~~~~~~~~~ For optional dependencies, prepend the service type with a ``?`` to prevent -errors if there's no matching service in the service container:: +errors if there's no matching service found in the service container:: use Psr\Log\LoggerInterface; @@ -306,7 +307,7 @@ include as many services as needed in it. The services defined in the service locator argument must include keys, which later become their unique identifiers inside the locator. -Now you can use the service locator injecting it in any other service: +Now you can use the service locator by injecting it in any other service: .. configuration-block:: From b1f0343ad2e7f8a456fefb87f206daa40a87a671 Mon Sep 17 00:00:00 2001 From: Tim Goudriaan Date: Wed, 18 Apr 2018 13:02:13 +0200 Subject: [PATCH 3/3] Incorporate feedback --- service_container/lazy_services.rst | 2 +- ...ribers.rst => service_subscribers_locators.rst} | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) rename service_container/{service_subscribers.rst => service_subscribers_locators.rst} (97%) diff --git a/service_container/lazy_services.rst b/service_container/lazy_services.rst index e698ceda679..e28877c7991 100644 --- a/service_container/lazy_services.rst +++ b/service_container/lazy_services.rst @@ -6,7 +6,7 @@ Lazy Services .. seealso:: - Another way to inject services lazily is via a :doc:`service subscriber `. + Another way to inject services lazily is via a :doc:`service subscriber `. Why Lazy Services? ------------------ diff --git a/service_container/service_subscribers.rst b/service_container/service_subscribers_locators.rst similarity index 97% rename from service_container/service_subscribers.rst rename to service_container/service_subscribers_locators.rst index d38cb883a25..c8df91c0404 100644 --- a/service_container/service_subscribers.rst +++ b/service_container/service_subscribers_locators.rst @@ -1,8 +1,8 @@ .. index:: single: DependencyInjection; Service Subscribers -Service Subscribers -=================== +Service Subscribers & Locators +============================== Sometimes, a service needs access to several other services without being sure that all of them will actually be used. In those cases, you may want the @@ -85,7 +85,7 @@ Defining a Service Subscriber ----------------------------- First, turn ``CommandBus`` into an implementation of :class:`Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface`. -Use its ``getSubscribedServices`` method to include as many services as needed +Use its ``getSubscribedServices()`` method to include as many services as needed in the service subscriber and change the type hint of the container to a PSR-11 ``ContainerInterface``:: @@ -141,7 +141,7 @@ which implements the PSR-11 ``ContainerInterface``, but it is also a callable:: return $handler->handle($command); -Including services +Including Services ------------------ In order to add a new dependency to the service subscriber, use the @@ -158,7 +158,7 @@ service locator:: ]; } -Service types can also be keyed by a service name for use internally:: +Service types can also be keyed by a service name for internal use:: use Psr\Log\LoggerInterface; @@ -170,7 +170,7 @@ Service types can also be keyed by a service name for use internally:: ]; } -Optional services +Optional Services ~~~~~~~~~~~~~~~~~ For optional dependencies, prepend the service type with a ``?`` to prevent @@ -191,7 +191,7 @@ errors if there's no matching service found in the service container:: Make sure an optional service exists by calling ``has()`` on the service locator before calling the service itself. -Aliased services +Aliased Services ~~~~~~~~~~~~~~~~ By default, autowiring is used to match a service type to a service from the