通过iTable-注入标记的服务从类名称中获取服务



我正在努力从注射标记的服务组中通过类名来获得特定的服务。

这是一个示例:我将实现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');
}

最新更新