Contributed by
Robin Chalas
Nicolas Grekas
in #21553
and #22024.
在Symfony程序中,某些服务需要访问若干其他服务,尽管那些服务并没有被真正用到(如 FirewallMap
类)。把所有这些未用到的服务进行实例化是无用的,但又不可能通过显式地依赖注入把它们变成lazy services。
这种场合下的传统解决方案是,注入整个服务容器,以便只获取到真正所需之服务。然而,并不推荐这样做,因为这给了服务一个过于宽泛的访问程序其他部分的机会,却隐藏了这些服务真正依赖。
Service locators 是一种设计模式,"encapsulate the processes involved in obtaining a service [...] using a central registry known as the service locator"(封装那些涉及到获取某个服务的进程[...],使用一个集中登记处,即service locator)。这个模式一般不被提倡,但在此处的场合却十分有用,它这种方式好过注入整个容器。
思考 CommandBus
类,它映射了命令行及其handlers。这个类一次只处理一条命令,因此将它们全部实例化并没有用。首先,定义一个service locator服务,使用全新的 container.service_locator
标签,再把全部服务作为该服务的参数:
1 2 3 4 5 6 7 8 9 | # app/config/services.yml
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' |
然后,注入service locator到定义为command bus的服务之中:
1 2 3 4 | # app/config/services.yml
services:
AppBundle\CommandBus:
arguments: ['@app.command_handler_locator'] |
被注入的service locator是一个 Symfony\Component\DependencyInjection\ServiceLocator
的实例。这个类实现的是PSR-11 ContainerInterface
接口,该接口包括了 has()
和 get()
方法以从locator检查并获取服务:
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 | // ...
use Psr\Container\ContainerInterface;
class CommandBus
{
/** @var ContainerInterface */
private $handlerLocator;
// ...
public function handle(Command $command)
{
$commandClass = get_class($command);
// check if some service is included in the service locator
// 检查是否有服务被包容到service locator中
if (!$this->handlerLocator->has($commandClass)) {
return;
}
// get the service from the service locator (and instantiate it)
// 从service locator中获取服务(并实例化之)
$handler = $this->handlerLocator->get($commandClass);
return $handler->handle($command);
}
} |