循环子类时如何使用自动装配?
How to use AutoWiring when looping through Subclasses?
我有一个 Sumfony 4.3 命令可以处理一些数据并循环通过多个 "processors" 来进行处理。该代码使用工厂(自动装配),然后实例化命令。
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);
}
不过我还是要:
- 自动装配工厂和处理器顶部的所有参数class
- 以正确的顺序将所有参数传递给处理器
我有大约 70 个子class 处理器。他们都使用 EntityInterface
,但只有几个使用 SwiftMailer
和其他依赖项。
由于我正在添加仅供少数处理器使用的服务,因此我正在寻找一种仅在处理器级别自动装配这些参数的方法。理想情况下,也不向 services.yml
添加服务定义
总而言之,我希望能够向 Processor
的任何子 class 添加依赖项,即使它是其他子[=32] 的父 class =]es 并自动注入依赖项。
在您的代码中有很多地方不是很明显,但是解决这个问题的典型方法是使用 "service locator"。 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()
方法。
现在我们将 "tag" 所有实现 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');
总结一下,基本上你需要的是:
- 为处理器定义共享接口
- 使用该接口标记所有处理器服务
- 为每个处理器定义一个
getDefaultIndexName()
,这有助于您将文件与处理器匹配。
- 在需要使用此服务的class中注入标记服务定位器
并且您可以让所有服务自动连接。
注意:您可以使用抽象 class 而不是接口,它的工作方式相同。我更喜欢使用界面,但这取决于您。
为完成起见,here is a repo 上述适用于 Symfony 4.3。
我有一个 Sumfony 4.3 命令可以处理一些数据并循环通过多个 "processors" 来进行处理。该代码使用工厂(自动装配),然后实例化命令。
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);
}
不过我还是要:
- 自动装配工厂和处理器顶部的所有参数class
- 以正确的顺序将所有参数传递给处理器
我有大约 70 个子class 处理器。他们都使用 EntityInterface
,但只有几个使用 SwiftMailer
和其他依赖项。
由于我正在添加仅供少数处理器使用的服务,因此我正在寻找一种仅在处理器级别自动装配这些参数的方法。理想情况下,也不向 services.yml
添加服务定义总而言之,我希望能够向 Processor
的任何子 class 添加依赖项,即使它是其他子[=32] 的父 class =]es 并自动注入依赖项。
在您的代码中有很多地方不是很明显,但是解决这个问题的典型方法是使用 "service locator"。 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()
方法。
现在我们将 "tag" 所有实现 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');
总结一下,基本上你需要的是:
- 为处理器定义共享接口
- 使用该接口标记所有处理器服务
- 为每个处理器定义一个
getDefaultIndexName()
,这有助于您将文件与处理器匹配。 - 在需要使用此服务的class中注入标记服务定位器
并且您可以让所有服务自动连接。
注意:您可以使用抽象 class 而不是接口,它的工作方式相同。我更喜欢使用界面,但这取决于您。
为完成起见,here is a repo 上述适用于 Symfony 4.3。