Symfony, как использовать AutoWiring при циклическом просмотре подклассов
У меня есть команда, которая обрабатывает некоторые данные и проходит через несколько "процессоров" для выполнения обработки. Код выглядит так:
class ProcessFilesCommand extends ContainerAwareCommand
{
private $processorFactory;
public function __construct(string $name = null, ProcessorFactory $processorFactory)
{
parent::__construct($name);
$this->processorFactory = $processorFactory;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$totalStart = microtime(true);
while(microtime(true)-$totalStart <= 58) {
if($lockUpload->acquire()) {
$file = $em->getRepository( 'App:ImportedFile' )->findBy(
['processed' => null],
['created' => 'ASC']
);
if (sizeof( $file ) > 0) {
$file = $file[0];
$processorFound = false;
foreach ($this->processorFactory->getAvailableProcessors() as $processor) {
$start = microtime( true );
if ($this->processorFactory->getInstance( $processor )
->setOutput( $output )
->setContainer( $this->getContainer() )
->setDoctrine( $this->getContainer()->get( 'doctrine' ) )
->setImportedFile($file)
->process()
!== false
) {
...
В моем цикле я должен назначить различные зависимости для моего процессора, некоторые из которых могут быть автоматически подключены. Как бы я использовал автопроводку в этом случае, так как имена моих классов не статичны, а в массиве.
Версия Symfony - 4.1
0 ответов
В вашем коде многое не сразу становится очевидным, но типичный способ решить эту проблему - использовать "локатор служб". Документы.
Представим, что у вас есть несколько сервисов, реализующих интерфейс. 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' }
Внимание: в документации сказано, что если вы определите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()
для каждого процессора, что помогает сопоставлять файлы с процессорами. - Внедрить помеченный локатор службы в класс, который должен использовать эти службы
И вы можете оставить все сервисы подключенными автоматически.
Примечание: вы можете использовать абстрактный класс вместо интерфейса, и он будет работать таким же образом. Я предпочитаю использовать интерфейс, но решать вам.
Для завершения, вот репо с вышеперечисленным, работающим для Symfony 4.3.