通过类名从可迭代获得服务-注入标记的服务 [英] Get service via class name from iterable - injected tagged services

查看:43
本文介绍了通过类名从可迭代获得服务-注入标记的服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力通过类别名称从注入的已标记服务组中获取特定服务.

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 Symfony\Component\DependencyInjection\ServiceLocator;
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 Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆