如何在循环遍历子类时使用自动连接?

人气:396 发布:2022-10-16 标签: php dependency-injection symfony symfony4 symfony-dependency-injection

问题描述

我有一个Sumfony 4.3命令,它处理一些数据并循环通过许多"处理器"来进行处理。代码使用工厂(自动连接),然后实例化该命令。

use AppEntityImportedFile;
use AppServiceProcessorProcessor;

class Factory implements FactoryInterface
{
    /** @var  array */
    private $processors;

    /** @var TestClausesInterface  */
    private $testClauses;

    private $em;
    private $dataSetProvider;
    private $ndviFromNasaService;
    private $archivalHashService;
    private $mailer;
    private $projectDir;

    public function __construct(
        TestClausesInterface $testClauses,
        ValidProcessorList $processors,
        EntityManagerInterface $em,
        DataSetProvider $dataSetProvider,
        NDVIFromNasaService $ndviFromNasaService,
        ArchivalHashService $archivalHashService,
        Swift_Mailer $mailer,
        $projectDir)
    {
        $this->processors = $processors;
        $this->testClauses = $testClauses;
        $this->em = $em;
        $this->dataSetProvider = $dataSetProvider;
        $this->ndviFromNasaService = $ndviFromNasaService;
        $this->archivalHashService = $archivalHashService;
        $this->mailer = $mailer;
        $this->projectDir = $projectDir;
    }

    public function findProcessorForFile(ImportedFile $file)
    {
        ...

        if ($found){
            $candidates = $this->recursive_scan( $this->projectDir.'/src/Processor');
            foreach ($candidates as $candidate){
                if (substr($candidate,0,strlen('Helper')) === 'Helper'){
                    continue;
                }
                try {
                    $candidate = str_replace($this->projectDir.'/src/Processor/', '', $candidate);
                    $candidate = str_replace('/','\', $candidate);
                    $testClassName = '\App\Processor\'.substr( $candidate, 0, -4 );
                    /* @var Processor $test */
                    if (!strstr($candidate, 'Helper')) {
                        $test = new $testClassName($this->testClauses, $this->em, $this->dataSetProvider, $this->ndviFromNasaService, $this->archivalHashService, $this->mailer, $this->projectDir);
                    }

然而,我仍然必须:

自动绑定工厂和处理器顶级类中的所有参数 按正确顺序将所有参数传递给处理器

我有大约70个子处理器类。它们都使用EntityInterface,但只有几个使用SwiftMailer和其他依赖项。

因为我正在添加仅供少数处理器使用的服务,所以我正在寻找一种仅在处理器级别自动绑定这些参数的方法。理想情况下,也不需要向services.yml添加服务定义

总之,我希望能够向Processor的任何子类添加依赖项,即使它是其他子类的父类并自动注入依赖项也是如此。

推荐答案

您的代码中有许多不是很明显的东西,但解决这个问题的典型方法是使用"服务定位器"。Docs。

假设您有几个实现接口的服务Processor

界面:

interface Processor {
    public function process($file): void;
}

组合实现:

class Foo implements Processor
{
    public function __construct(DataSetProvider $dataSet, ArchivalHashService $archivalHash, Swift_Mailer $swift) {
        // initialize properties
    }

    public function process($file) {
        // process implementation
    }

    public static function getDefaultIndexName(): string
    {
        return 'candidateFileOne';
    }
}

两个实现:

class Bar implements Processor
{
    public function __construct(Swift_Mailer $swift, EntityManagerInterface $em) {
        // initialize properties
    }

    public function process($file) {
        // process implementation
    }

    public static function getDefaultIndexName(): string
    {
        return 'candidateFileTwo';
    }
}

请注意,每个处理器具有完全不同的依赖关系,并且可以直接自动连接,并且每个处理器都有一个getDefaultIndexName()方法。

现在我们将"标记"实现Processor接口的所有服务:

# services.yaml
services:
    # somewhere below the _defaults and the part where you make all classes in `src` available as services
    _instanceof:
        AppProcessor:
            tags:
                - { name: "processor_services", default_index_method: 'getDefaultIndexName' }

注意这里:文档says如果您定义了public static function getDefaultIndexName(),它将被默认选中。但我发现这个目前不起作用。但是,如果您定义default_index_method,则可以将其连接到您选择的方法。我暂时保留getDefaultIndexName,但您可以随意挑选。

现在,如果您需要在控制台命令中执行此进程,例如:

use SymfonyComponentDependencyInjectionServiceLocator;

class MyConsoleCommand
{
    private ServiceLocator $locator;

    public function __construct(ServiceLocator $locator)
    {
        $this->locator = $locator;
    }

}

要注入服务定位器,请执行以下操作:

#services.yaml

services:
    AppHandlerCollection:
        arguments: [!tagged_locator { tag: 'processor_services' } ]

要从服务定位器获取任何处理器,您将执行以下操作:

$fooProcessor = $this->locator->get('candidateFileOne');
$barProcessor = $this->locator->get('candidateFileTwo');

总而言之,基本上您需要的是:

为处理器定义共享接口 使用该接口标记所有处理器服务 为每个处理器定义getDefaultIndexName(),这有助于将文件与处理器匹配。 在需要使用此服务的类中注入标记的服务定位器

并且您可以让所有服务保持自动布线。

注意:您可以使用抽象类而不是接口,它的工作方式也是一样的。我更喜欢使用界面,但这取决于您。

为完整起见,here is a repo以上适用于symfony 4.3。

664