From 5efacd073072580d924de05c3e3052b427374744 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 9 Feb 2017 23:06:23 +0100 Subject: [PATCH 1/3] [DI] Add section about Service Locators --- service_container/service_locators.rst | 227 +++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 service_container/service_locators.rst diff --git a/service_container/service_locators.rst b/service_container/service_locators.rst new file mode 100644 index 00000000000..bb8015a7fb0 --- /dev/null +++ b/service_container/service_locators.rst @@ -0,0 +1,227 @@ +.. index:: + single: DependencyInjection; Service Locators + +Service Locators +================ + +What is a Service Locator +------------------------- + +Sometimes, a service needs the ability to access other services without being sure +that all of them will actually be used. + +In such cases, you may want the instantiation of these services to be lazy, that is +not possible using explicit dependency injection since services are not all meant to +be ``lazy`` (see :doc:`/service_container/lazy_services`). + +A real-world example being a CommandBus which maps command handlers by Command +class names and use them to handle their respective command when it is asked for:: + + // ... + class CommandBus + { + /** + * @var CommandHandler[] + */ + private $handlerMap; + + public function __construct(array $handlerMap) + { + $this->handlerMap = $handlerMap; + } + + public function handle(Command $command) + { + $commandClass = get_class($command) + + if (!isset($this->handlerMap[$commandClass])) { + return; + } + + return $this->handlerMap[$commandClass]->handle($command); + } + } + + // ... + $commandBus->handle(new FooCommand()); + +Because only one command is handled at a time, other command handlers are not +used but unnecessarily instantiated. + +A solution allowing to keep handlers lazily loaded could be to inject the whole +dependency injection container:: + + use Symfony\Component\DependencyInjection\ContainerInterface; + + class CommandBus + { + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function handle(Command $command) + { + $commandClass = get_class($command) + + if ($this->container->has($commandClass)) { + $handler = $this->container->get($commandClass); + + return $handler->handle($command); + } + } + } + +But injecting the container has many drawbacks including: + +- too broad access to existing services +- services which are actually useful are hidden + +Service Locators are intended to solve this problem by giving access to a set of +identified services while instantiating them only when really needed. + +Configuration +------------- + +For injecting a service locator into your service(s), you first need to register +the service locator itself as a service using the `container.service_locator` +tag: + +.. configuration-block:: + + .. code-block:: yaml + + services: + + app.command_handler_locator: + class: Symfony\Component\DependencyInjection\ServiceLocator + arguments: + AppBundle\FooCommand: '@app.command_handler.foo' + AppBundle\BarCommand: '@app.command_handler.bar' + tags: ['container.service_locator'] + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + use Symfony\Component\DependencyInjection\ServiceLocator; + use Symfony\Component\DependencyInjection\Reference; + + //... + + $container + ->register('app.command_handler_locator', ServiceLocator::class) + ->addTag('container.service_locator') + ->setArguments(array( + 'AppBundle\FooCommand' => new Reference('app.command_handler.foo'), + 'AppBundle\BarCommand' => new Reference('app.command_handler.bar'), + )) + ; + +.. note:: + + The services defined in the service locator argument must be keyed. + Those keys become their unique identifier inside the locator. + + +Now you can use it in your services by injecting it as needed: + +.. configuration-block:: + + .. code-block:: yaml + + services: + + AppBundle\CommandBus: + arguments: ['@app.command_handler_locator'] + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + use AppBundle\CommandBus; + use Symfony\Component\DependencyInjection\Reference; + + //... + + $container + ->register(CommandBus::class) + ->setArguments(array(new Reference('app.command_handler_locator'))) + ; + +.. tip:: + + You should create and inject the service locator as an anonymous service if + it is not intended to be used by multiple services + +Usage +----- + +Back to our CommandBus which now looks like:: + + // ... + 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); From f5e49424a9a161f1ad757f69bb45a0ffe16e8c3a Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 28 Apr 2017 17:54:20 +0200 Subject: [PATCH 2/3] Rewords --- service_container/service_locators.rst | 62 ++++++++++++-------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/service_container/service_locators.rst b/service_container/service_locators.rst index bb8015a7fb0..8afba0a7d10 100644 --- a/service_container/service_locators.rst +++ b/service_container/service_locators.rst @@ -4,18 +4,15 @@ Service Locators ================ -What is a Service Locator -------------------------- - -Sometimes, a service needs the ability to access other services without being sure -that all of them will actually be used. - -In such cases, you may want the instantiation of these services to be lazy, that is -not possible using explicit dependency injection since services are not all meant to +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 +instantiation of the services to be lazy. However, that's not possible using +the explicit dependency injection since services are not all meant to be ``lazy`` (see :doc:`/service_container/lazy_services`). -A real-world example being a CommandBus which maps command handlers by Command -class names and use them to handle their respective command when it is asked for:: +A real-world example are applications that implement the `Command pattern`_ +using a CommandBus to map command handlers by Command class names and use them +to handle their respective command when it is asked for:: // ... class CommandBus @@ -45,11 +42,9 @@ class names and use them to handle their respective command when it is asked for // ... $commandBus->handle(new FooCommand()); -Because only one command is handled at a time, other command handlers are not -used but unnecessarily instantiated. - -A solution allowing to keep handlers lazily loaded could be to inject the whole -dependency injection container:: +Considering that only one command is handled at a time, instantiating all the +other command handlers is unnecessary. A possible solution to lazy-load the +handlers could be to inject the whole dependency injection container:: use Symfony\Component\DependencyInjection\ContainerInterface; @@ -74,20 +69,19 @@ dependency injection container:: } } -But injecting the container has many drawbacks including: +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. -- too broad access to existing services -- services which are actually useful are hidden +**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 Locators are intended to solve this problem by giving access to a set of -identified services while instantiating them only when really needed. +Defining a Service Locator +-------------------------- -Configuration -------------- - -For injecting a service locator into your service(s), you first need to register -the service locator itself as a service using the `container.service_locator` -tag: +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: .. configuration-block:: @@ -138,11 +132,10 @@ tag: .. note:: - The services defined in the service locator argument must be keyed. - Those keys become their unique identifier inside the locator. - + The services defined in the service locator argument must include keys, + which later become their unique identifiers inside the locator. -Now you can use it in your services by injecting it as needed: +Now you can use the service locator injecting it in any other service: .. configuration-block:: @@ -183,13 +176,14 @@ Now you can use it in your services by injecting it as needed: .. tip:: - You should create and inject the service locator as an anonymous service if - it is not intended to be used by multiple services + 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 our CommandBus which now looks like:: +Back to the previous CommandBus example, it looks like this when using the +service locator:: // ... use Psr\Container\ContainerInterface; @@ -225,3 +219,5 @@ which implements the PSR-11 ``ContainerInterface``, but it is also a callable:: $handler = $locateHandler($commandClass); return $handler->handle($command); + +.. _`Command pattern`: https://en.wikipedia.org/wiki/Command_pattern From fa1977024bb314bb8acd340a502f5ea802152e06 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 3 May 2017 10:11:42 +0200 Subject: [PATCH 3/3] Fix service locator declaration --- service_container/service_locators.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/service_container/service_locators.rst b/service_container/service_locators.rst index 8afba0a7d10..25ee199efef 100644 --- a/service_container/service_locators.rst +++ b/service_container/service_locators.rst @@ -91,10 +91,11 @@ option to include as many services as needed to it and add the app.command_handler_locator: class: Symfony\Component\DependencyInjection\ServiceLocator - arguments: - AppBundle\FooCommand: '@app.command_handler.foo' - AppBundle\BarCommand: '@app.command_handler.bar' tags: ['container.service_locator'] + arguments: + - + AppBundle\FooCommand: '@app.command_handler.foo' + AppBundle\BarCommand: '@app.command_handler.bar' .. code-block:: xml @@ -106,8 +107,10 @@ option to include as many services as needed to it and add the - - + + + + @@ -124,10 +127,10 @@ option to include as many services as needed to it and add the $container ->register('app.command_handler_locator', ServiceLocator::class) ->addTag('container.service_locator') - ->setArguments(array( + ->setArguments(array(array( 'AppBundle\FooCommand' => new Reference('app.command_handler.foo'), 'AppBundle\BarCommand' => new Reference('app.command_handler.bar'), - )) + ))) ; .. note::