我正在努力从注射标记的服务组中通过类名来获得特定的服务。
这是一个示例:我将实现DriverInterface
的所有服务标记为app.driver
并将其绑定到$drivers
变量。
在其他一些服务中,我需要获取所有标记为app.driver
的驱动程序,并仅使用它们中的所有驱动程序。但是需要什么驱动程序是动态的。
services.yml
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$drivers: [!tagged app.driver]
_instanceof:
DriverInterface:
tags: ['app.driver']
其他一些服务:
/**
* @var iterable
*/
private $drivers;
/**
* @param iterable $drivers
*/
public function __construct(iterable $drivers)
{
$this->drivers = $drivers;
}
public function getDriverByClassName(string $className): DriverInterface
{
????????
}
因此,实现DriverInterface
的服务将其注入$this->drivers
参数为迭代结果。我只能通过它们foreach
,但是所有服务都将实例化。
是否有其他方法可以从他们实例化的情况下通过class Name注入这些服务以获取特定的服务?
我知道有可能使这些驱动程序公开并使用容器,但是我想避免将容器注入服务中,如果有可能其他方式。
您不再(由于Symfony 4)需要创建一个编译器通行以配置服务定位器。
可以通过配置来完成所有操作,让Symfony执行"魔术"。
您可以使用以下配置添加:
services:
_instanceof:
DriverInterface:
tags: ['app.driver']
lazy: true
DriverConsumer:
arguments:
- !tagged_locator
tag: 'app.driver'
需要访问这些服务而不是接收iterable
的服务,接收ServiceLocatorInterface
:
class DriverConsumer
{
private $drivers;
public function __construct(ServiceLocatorInterface $locator)
{
$this->locator = $locator;
}
public function foo() {
$driver = $this->locator->get(Driver::class);
// where Driver is a concrete implementation of DriverInterface
}
}
和就是。您不需要其他任何东西,它只能起作用 tm 。
完整的示例
与所有涉及的所有类的完整示例。
我们有:
FooInterface
:
interface FooInterface
{
public function whoAmI(): string;
}
AbstractFoo
简化实施,这是一个抽象类,我们将在具体服务中扩展:
abstract class AbstractFoo implements FooInterface
{
public function whoAmI(): string {
return get_class($this);
}
}
服务实施
实现FooInterface
class FooOneService extends AbstractFoo { }
class FooTwoService extends AbstractFoo { }
服务的消费者
和另一个需要服务定位器使用我们刚定义的两个服务:
class Bar
{
/**
* @var SymfonyComponentDependencyInjectionServiceLocator
*/
private $service_locator;
public function __construct(ServiceLocator $service_locator) {
$this->service_locator = $service_locator;
}
public function handle(): string {
/** @var AppTestFooInterface $service */
$service = $this->service_locator->get(FooOneService::class);
return $service->whoAmI();
}
}
配置
唯一需要的配置是:
services:
_instanceof:
AppTestFooInterface:
tags: ['test_foo_tag']
lazy: true
AppTestBar:
arguments:
- !tagged_locator
tag: 'test_foo_tag'
服务名称的FQCN替代品
如果您不使用要定义自己的服务名称的类名称,则可以使用静态方法来定义服务名称。配置将更改为:
AppTestBar:
arguments:
- !tagged_locator
tag: 'test_foo_tag'
default_index_method: 'fooIndex'
fooIndex
是在返回字符串的每个服务上定义的公共静态方法。注意:如果您使用此方法,您将无法通过其班级名称获得服务。
服务启动器将允许通过名称访问服务,而无需实例化其余部分。它确实需要编译器通行证,但设置并不难。
use SymfonyComponentDependencyInjectionServiceLocator;
class DriverLocator extends ServiceLocator
{
// Leave empty
}
# Some Service
public function __construct(DriverLocator $driverLocator)
{
$this->driverLocator = $driverLocator;
}
public function getDriverByClassName(string $className): DriverInterface
{
return $this->driverLocator->get($fullyQualifiedClassName);
}
现在是魔术:
# src/Kernel.php
# Make your kernel a compiler pass
use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
class Kernel extends BaseKernel implements CompilerPassInterface {
...
# Dynamically add all drivers to the locator using a compiler pass
public function process(ContainerBuilder $container)
{
$driverIds = [];
foreach ($container->findTaggedServiceIds('app.driver') as $id => $tags) {
$driverIds[$id] = new Reference($id);
}
$driverLocator = $container->getDefinition(DriverLocator::class);
$driverLocator->setArguments([$driverIds]);
}
和presto。假设您修复了我可能引入的任何语法错误或错别字。
,要获得额外的信用,您可以自动注册您的驾驶员类,并在服务文件中摆脱该实例。
# Kernel.php
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(DriverInterface::class)
->addTag('app.driver');
}