【问题标题】:How to use AutoWiring when looping through Subclasses?循环子类时如何使用自动装配?
【发布时间】:2018-12-12 01:12:29
【问题描述】:

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

use App\Entity\ImportedFile;
use App\Service\Processor\Processor;

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);
                    }

但我仍然必须:

  • 自动装配 Factory 和 Processor 顶级类中的所有参数
  • 将所有参数按正确顺序传递给处理器

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

由于我正在添加仅供少数处理器使用的服务,因此我正在寻找一种仅在处理器级别自动装配这些参数的方法。理想情况下,也无需向 services.yml 添加服务定义

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

【问题讨论】:

  • 不是肯定的,但可能是 ProcessServiceLocator 而不是流程工厂。基本上,流程定位器将包含可用流程服务的列表。 symfony.com/doc/current/service_container/…
  • Symfony 自动连接将我作为参数放入构造函数中的任何内容。我想在这里实现同样的目标。当我向处理器类的构造函数添加参数时,我希望注入此服务,否则不会。然后我仍然会从工厂注入实体管理器,但这与解决方案无关
  • 我想差不多了。连接服务定位器似乎工作。我在数据库表中使用文件前缀间接确定哪个处理器处理文件,因此我使用 strval($type->getId()) 与公共静态函数 getDefaultIndexName() 匹配。但是服务定位器没有找到处理器,当我调试时,我看不到我的 getDefaultIndexName() 返回值是如何在任何地方索引的

标签: php symfony dependency-injection symfony4 symfony-dependency-injection


【解决方案1】:

在您的代码中有很多不是很明显的,但是解决这个问题的典型方法是使用“服务定位器”。 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:
        App\Processor:
            tags:
                - { name: "processor_services", default_index_method: 'getDefaultIndexName' }

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

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

use Symfony\Component\DependencyInjection\ServiceLocator;

class MyConsoleCommand
{
    private ServiceLocator $locator;

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

}

要注入服务定位器,您会这样做:

#services.yaml

services:
    App\HandlerCollection:
        arguments: [!tagged_locator { tag: 'processor_services' } ]

要从服务定位器中获取任何处理器,您会这样做:

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

总结一下,基本上你需要的是:

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

您可以让所有服务自动连接。

注意:您可以使用抽象类代替接口,它的工作方式相同。我更喜欢使用界面,但这取决于你。

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

【讨论】:

  • 不,它不起作用。我只能通过其完全限定的类名来获取处理器。还将作曲家更新到最新的 4.3
  • 还尝试将 getDefaultIndexName 放在不带引号的位置,如下所示:olvlvl.com/2019-09-symfony-tagged-service-locator
  • 您的代码中一定有其他类型的错误。我在 Sf 4.3 和 Sf 5 上测试了上述内容。它有效。我已经设置了a repo,您可以在其中看到它的运行情况。只需克隆它,执行composer install,然后运行bin/console foobar,它就会工作。只有几门课,所以没有什么神秘之处。
  • 附录:getDefaultIndexName中指定的方法需要返回一个字符串,似乎strval(number)不够用。如果返回的键不是字符串,标记的服务将按找到的顺序或完整的类名进行索引,并且可能无法正确匹配,或者调用 serviceLocator->get 将返回错误
  • 你也是一个命中的 PHP 限制。在关联数组中使用数字字符串作为键既不安全也不明智。例如。见this
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-06
  • 2019-05-17
  • 2014-06-26
  • 2015-12-03
  • 2011-07-23
  • 2017-10-19
  • 1970-01-01
相关资源
最近更新 更多