Локаторы сервисов¶
Иногда сервису требуется получить доступ к некоторым другим сервисам, даже не
имея уверенности в том, что все из них действительно будут использованы. В таких
случаях, вы можете захотеть, чтобы инстанциирование сервисов было ленивым. Однако,
это невозможно при использовании ясного внедрения зависимости, так как сервисы не
предназначены для того, чтобы быть ленивыми (lazy
) (см. Lazy Services).
Реальным примером являются приложения, которые реализуют Команду (шаблон проектирования), используя CommandBus для соединения обработчиков команд с именами классов Команды, и используют их для того, чтобы обрабатывать соответствующую команду, когда она запрошена:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // ...
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());
|
Учитывая, что одновременно обрабатывается только одна команда, инстанциирование всех других обработчиков команд необязательно. Возможным решением для ленивой загрузки обработчиков может стать внедрение всего контейнера внедрения зависимостей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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);
}
}
}
|
Однако, внедрение всего контейнера не поощряется, так как это предоставляет слишком широкий доступ к существующим сервисам и скрывает настоящие зависимости сервисов.
Локаторы сервисов предназначены для решения этой проблемы, путём предоставления доступа к набору предопределённых сервисов, инстанциируя их только тогда, когда они действительно необходимы.
Определение локатора сервиса¶
Для начала, определите новый сервис для локатора сервиса. Используйте его
опцию arguments
, чтобы включить в него такое количество сервисов, которое
необходимо, и добавьте тег container.service_locator
, чтобы превратить
его в локатор сервисов:
- YAML
1 2 3 4 5 6 7 8 9
services: app.command_handler_locator: class: Symfony\Component\DependencyInjection\ServiceLocator tags: ['container.service_locator'] arguments: - AppBundle\FooCommand: '@app.command_handler.foo' AppBundle\BarCommand: '@app.command_handler.bar'
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="app.command_handler_locator" class="Symfony\Component\DependencyInjection\ServiceLocator"> <argument type="collection"> <argument key="AppBundle\FooCommand" type="service" id="app.command_handler.foo" /> <argument key="AppBundle\BarCommand" type="service" id="app.command_handler.bar" /> </argument> <tag name="container.service_locator" /> </service> </services> </container>
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\DependencyInjection\Reference; //... $container ->register('app.command_handler_locator', ServiceLocator::class) ->addTag('container.service_locator') ->setArguments(array(array( 'AppBundle\FooCommand' => new Reference('app.command_handler.foo'), 'AppBundle\BarCommand' => new Reference('app.command_handler.bar'), ))) ;
Note
Сервисы, определённые в аргументе локатора сервисов должны иметь ключи, которые позже становятся уникальными идентификаторами внутри локатора.
Теперь вы можете использовать локатор сервисов, внедряя его в любой другой сервис:
- YAML
1 2 3 4
services: AppBundle\CommandBus: arguments: ['@app.command_handler_locator']
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="AppBundle\CommandBus"> <argument type="service" id="app.command_handler.locator" /> </service> </services> </container>
- PHP
1 2 3 4 5 6 7 8 9
use AppBundle\CommandBus; use Symfony\Component\DependencyInjection\Reference; //... $container ->register(CommandBus::class) ->setArguments(array(new Reference('app.command_handler_locator'))) ;
Tip
Если локатор сервисов не предназначен для использования многими сервисами, то лучше создать и внедрить его в качестве анонимного сервиса.
Использование¶
Вернёмся к предыдущему примеру с CommandBus, вот так он выглядит при использовании локатора сервисов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // ...
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);
}
}
|
Внедрённый сервис - экземпляр ServiceLocator
,
который реализует PSR-11 ContainerInterface
, но также является вызваемым:
1 2 3 4 5 | // ...
$locateHandler = $this->handlerLocator;
$handler = $locateHandler($commandClass);
return $handler->handle($command);
|
Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.