通过可迭代的类名获取服务 - 注入的标记服务 [英] Get service via class name from iterable - injected tagged services
问题描述
我正在努力通过类名从注入的标记服务组中获取特定服务.
I am struggling to get a specific service via class name from group of injected tagged services.
这是一个例子:我将所有实现 DriverInterface
的服务标记为 app.driver
并将其绑定到 $drivers
变量.
Here is an example:
I tag all the services that implement DriverInterface
as app.driver
and bind it to the $drivers
variable.
在其他一些服务中,我需要获取所有标记为 app.driver
的驱动程序,然后实例化并仅使用其中的几个.但是需要的驱动程序是动态的.
In some other service I need to get all those drivers that are tagged app.driver
and instantiate and use only few of them. But what drivers will be needed is dynamic.
services.yml
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
,但随后所有服务都将被实例化.
So services that implements DriverInterface
are injected to $this->drivers
param as iterable result. I can only foreach
through them, but then all services will be instantiated.
是否有其他方法可以注入这些服务,通过类名从它们那里获取特定服务,而无需实例化其他服务?
Is there some other way to inject those services to get a specific service via class name from them without instantiating others?
我知道有可能将这些驱动程序公开并使用容器,但如果可以通过其他方式实现,我希望避免将容器注入服务.
I know there is a possibility to make those drivers public and use container instead, but I would like to avoid injecting container into services if it's possible to do it some other way.
推荐答案
A ServiceLocator 将允许按名称访问服务,而无需实例化其余服务.它确实需要编译器通过,但设置起来并不难.
A ServiceLocator will allow accessing a service by name without instantiating the rest of them. It does take a compiler pass but it's not too hard to setup.
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]);
}
而且很快.假设您修复了我可能引入的任何语法错误或拼写错误,它应该可以工作.
And presto. It should work assuming you fix any syntax errors or typos I may have introduced.
为了获得额外的积分,您可以自动注册您的驱动程序类并删除服务文件中的那个 instanceof 条目.
And for extra credit, you can auto register your driver classes and get rid of that instanceof entry in your services file.
# Kernel.php
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(DriverInterface::class)
->addTag('app.driver');
}
这篇关于通过可迭代的类名获取服务 - 注入的标记服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!