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

Подводя итоги, в основном вам нужно:

  1. Определите общий интерфейс для процессоров
  2. Используйте этот интерфейс, чтобы пометить все службы процессора
  3. Определить getDefaultIndexName() для каждого процессора, что помогает сопоставлять файлы с процессорами.
  4. Внедрить помеченный локатор службы в класс, который должен использовать эти службы

И вы можете оставить все сервисы подключенными автоматически.

Примечание: вы можете использовать абстрактный класс вместо интерфейса, и он будет работать таким же образом. Я предпочитаю использовать интерфейс, но решать вам.

Для завершения, вот репо с вышеперечисленным, работающим для Symfony 4.3.

Другие вопросы по тегам