Получить сервис через имя класса из итерируемых сервисов с тегами

Я изо всех сил пытаюсь получить конкретную услугу через имя класса из группы внедренных помеченных услуг.

Вот пример: Я помечаю все сервисы, реализующие DriverInterface, как app.driver и привязываю их к переменной $drivers.

В каком-то другом сервисе мне нужно получить все те драйверы, которые помечены app.driver, создать экземпляр и использовать только некоторые из них. Но какие драйвера будут нужны, так это динамические.

services.yml

_defaults:
        autowire: true
        autoconfigure: true
        public: false
        bind:
            $drivers: [!tagged app.driver]

_instanceof:
        DriverInterface:
            tags: ['app.driver']

Некоторые другие услуги:

/**
 * @var iterable
 */
private $drivers;

/**
 * @param iterable $drivers
 */
public function __construct(iterable $drivers) 
{
    $this->drivers = $drivers;
}

public function getDriverByClassName(string $className): DriverInterface
{
    ????????
}

Таким образом, сервисы, реализующие DriverInterface, внедряются в $this->drivers param в качестве итерируемого результата. Я могу только foreach через них, но тогда все сервисы будут инстанцированы.

Есть ли другой способ внедрить эти службы, чтобы получить от них конкретную службу через имя класса без создания других?

Я знаю, что есть возможность сделать эти драйверы общедоступными и вместо этого использовать контейнер, но я хотел бы избежать внедрения контейнера в службы, если это возможно сделать каким-то другим способом.

Сделайте каждый драйвер ленивым сервисом.

emix 01.03.2019 15:33

В этом поможет новая функция, называемая индексированными службами: symfony.com/blog/… К сожалению, я думаю, что прямо сейчас либо нужно создать CompilerPass, если вы хотите решить эту проблему программно, либо добавить несколько тегов, например. в зависимости от папки, в которой хранятся службы, или вручную пометив каждую службу.

dbrumann 01.03.2019 15:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
7
2
2 829
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

СервисЛокатор позволит получить доступ к сервису по имени без создания экземпляра остальных. Это требует прохождения компилятора, но его не так уж сложно настроить.

use Symfony\Component\DependencyInjection\ServiceLocator;
class DriverLocator extends ServiceLocator
{
    // Leave empty
}
# Some Service
public function __construct(DriverLocator $driverLocator) 
{
    $this->driverLocator = $driverLocator;
}

public function getDriverByClassName(string $className): DriverInterface
{
    return $this->driverLocator->get($fullyQualifiedClassName);
}

Теперь идет волшебство:

# src/Kernel.php
# Make your kernel a compiler pass
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class Kernel extends BaseKernel implements CompilerPassInterface {
...
# Dynamically add all drivers to the locator using a compiler pass
public function process(ContainerBuilder $container)
{
    $driverIds = [];
    foreach ($container->findTaggedServiceIds('app.driver') as $id => $tags) {
        $driverIds[$id] = new Reference($id);
    }
    $driverLocator = $container->getDefinition(DriverLocator::class);
    $driverLocator->setArguments([$driverIds]);
}

И вуаля. Это должно работать, если вы исправите любые синтаксические ошибки или опечатки, которые я мог ввести.

И в качестве дополнительного кредита вы можете автоматически зарегистрировать свои классы драйверов и избавиться от этой записи instanceof в вашем файле сервисов.

# Kernel.php
protected function build(ContainerBuilder $container)
{
    $container->registerForAutoconfiguration(DriverInterface::class)
        ->addTag('app.driver');
}

Вам больше не нужно (начиная с Symfony 4) создавать проход компилятора для настройки локатора сервисов.

Все можно сделать с помощью конфигурации и позволить Symfony выполнять «волшебство».

Вы можете обойтись следующими дополнениями к вашей конфигурации:

services:
  _instanceof:
    DriverInterface:
      tags: ['app.driver']
      lazy: true

  DriverConsumer:
    arguments:
      - !tagged_locator
        tag: 'app.driver'

Сервис, которому нужен доступ к ним, вместо получения iterable получает ServiceLocatorInterface:

class DriverConsumer
{
    private $drivers;
    
    public function __construct(ServiceLocatorInterface $locator) 
    {
        $this->locator = $locator;
    }
    
    public function foo() {
        $driver = $this->locator->get(Driver::class);
        // where Driver is a concrete implementation of DriverInterface
    }
}

И Это оно. Вам больше ничего не нужно, это просто работаетtm.


Полный пример

Полный пример со всеми задействованными классами.

У нас есть:

FooInterface:

interface FooInterface
{
    public function whoAmI(): string;
}

AbstractFoo

Чтобы упростить реализацию, абстрактный класс, который мы будем расширять в наших конкретных сервисах:

abstract class AbstractFoo implements FooInterface
{
    public function whoAmI(): string {
        return get_class($this);
    }   
}

Реализации сервисов

Пара сервисов, которые реализуют FooInterface

class FooOneService extends AbstractFoo { }
class FooTwoService extends AbstractFoo { }

потребитель услуг

И еще один сервис, который требует, чтобы локатор сервисов использовал эти два, которые мы только что определили:

class Bar
{
    /**
     * @var \Symfony\Component\DependencyInjection\ServiceLocator
     */
    private $service_locator;

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

    public function handle(): string {
        /** @var \App\Test\FooInterface $service */
        $service = $this->service_locator->get(FooOneService::class);

        return $service->whoAmI();
    }
}

Конфигурация

Единственная необходимая конфигурация будет такой:

services:
  _instanceof:
    App\Test\FooInterface:
      tags: ['test_foo_tag']
      lazy: true
    
  App\Test\Bar:
      arguments:
        - !tagged_locator
          tag: 'test_foo_tag'
            

Альтернатива FQCN для имен служб

Если вместо использования имени класса вы хотите определить свои собственные имена служб, вы можете использовать статический метод для определения имени службы. Конфигурация изменится на:

App\Test\Bar:
        arguments:
          - !tagged_locator
            tag: 'test_foo_tag'
            default_index_method: 'fooIndex'

где fooIndex — общедоступный статический метод, определенный для каждой из служб, который возвращает строку. Внимание: если вы используете этот метод, вы не сможете получить сервисы по именам их классов.

Это элегантное решение, но следует отметить, что оно работает только в Symfony >=4. Для Symfony 3 вам все равно придется использовать проход компилятора

Sean 14.02.2020 18:37

Ты прав. «Больше не нужно» подразумевало это, но я должен сделать это явным. Установок Symfony <4 с каждым днем ​​становится все меньше и меньше, но об этом стоит упомянуть.

yivi 14.02.2020 18:39

Я работаю над устаревшей кодовой базой (3.4) и на самом деле пытаюсь сделать именно это, на полпути к вашему решению, прежде чем я понял, что это функция 4+. Грустное лицо.

Sean 14.02.2020 18:47

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