Как внедрить группу сервисов, которые реализуют один и тот же интерфейс, не объявляя разводку для каждого сервиса? - PullRequest
1 голос
/ 19 сентября 2019

Я разрабатываю приложение, в котором у меня есть несколько обработчиков в качестве сервисов, которые я хочу вызывать.Оба они реализуют ItemHandlerInterface.

. Я бы хотел, чтобы в контроллере можно было получать всю коллекцию ItemHandlerInterface сервисов , не подключая их вручную.

Пока что я пометил их специально:

services.yaml

_instanceof:
    App\Model\ItemHandlerInterface:
        tags: [!php/const App\DependencyInjection\ItemHandlersCompilerPass::ITEM_HANDLER_TAG]
        lazy: true

И попробуйте получить мою коллекцию сервисов в контроллере.Это работает, если только один сервис реализует ItemHandlerInterface, но как только я создаю несколько (как показано ниже TestHandler и Test2Handler, я получаю The service "service_locator.03wqafw.App\Controller\ItemUpdateController" has a dependency on a non-existent service "App\Model\ItemHandlerInterface".

Как я могудинамически извлекать все сервисы, реализующие мой интерфейс?

Одно грязное решение - заставить все ItemHandlerInterface с public: true и передать Container моему конструктору контроллера. Но это уродливо, и я хотел бычтобы найти более элегантный способ.

ItemUpdateController

namespace App\Controller;

use App\Model\ItemHandlerInterface;
use App\Service\ItemFinder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Debug\Exception\ClassNotFoundException;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use App\Model\Item;
use Psr\Container\ContainerInterface;

/**
 * Class ItemUpdateController
 *
 * @package App\Controller
 */
class ItemUpdateController extends AbstractController
{
    /**
     * @var ContainerInterface
     */
    protected $locator;

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

    public static function getSubscribedServices()
    {
        // Try to subscribe to all ItemHandlerInterface services
        return array_merge(
                parent::getSubscribedServices(),
                ['item_handler' => ItemHandlerInterface::class]
        );
    }

    /**
     * @param string $id
     * @param RequestStack $requestStack
     * @param ItemFinder $itemFinder
     *
     * @return Item
     * @throws \Symfony\Component\Debug\Exception\ClassNotFoundException
     */
    public function __invoke(
        string $id,
        RequestStack $requestStack,
        ItemFinder $itemFinder
    ) {
        // Find item
        $item = $itemFinder->findById($id);

        // Extract and create handler instance
        $handlerName = $item->getHandlerName();

        if($this->locator->has($handlerName)) {

            $handler = $this->locator->get($handlerName);
            $request = $requestStack->getCurrentRequest();
            $payload = json_decode($request->getContent());

            call_user_func($handler, $payload, $request);

            return $item;
        }
    }
}

src / ItemHandler / TestHandler.php

namespace App\ItemHandler;

use App\Model\ItemHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;

class TestHandler implements ItemHandlerInterface
{
// implementation
}

src / ItemHandler / Test2Handler.php

namespace App\ItemHandler;

use App\Model\ItemHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;

class Test2Handler implements ItemHandlerInterface
{
// implementation
}

Ответы [ 3 ]

1 голос
/ 19 сентября 2019

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

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

Поскольку вы уже делаете тегирование, как показано в вопросе, оно находится втолько вопрос объявления инъекции:

_instanceof:
    App\Model\ItemHandlerInterface:
        tags: ['item_handler']
        lazy: true

services:
    App\Controller\ItemUpdateController:
        arguments: [!tagged 'item_handler']

Реализация

Вам нужно изменить конструктор для вашего контроллера, чтобы он принимал iterable:

public function __construct(iterable $itemHandlers)
{
    $this->handlers = $itemHandlers;
}

Это было доступно начиная с 3.4 ; все еще поддерживается .


Extra

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

Вы можете прочитать подробнее здесь .

0 голосов
/ 19 сентября 2019

Когда я набирал это, я просто увидел, что ответ принят.Справедливо.В любом случае, это работает, и я пока оставлю это в качестве справки:

services:
   _instanceof:
        # Tag all your item handlers
        App\Model\ItemHandlerInterface:
            tags: [app.item_handler]

    # inject as an iterable into the controller
    App\Controller\IndexController:
        arguments: [!tagged app.item_handler]

То же, что и принятый ответ: https://symfony.com/blog/new-in-symfony-3-4-simpler-injection-of-tagged-services

Я также хотел быОтметим, что этот подход поддерживает только повторяемость.Если вы хотите получить произвольный доступ к определенному обработчику элементов (возможно, через имя класса) без создания экземпляра остальных, вам нужно создать свой собственный локатор класса, который требует немного больше усилий.

0 голосов
/ 19 сентября 2019

Хороший способ сделать это - использовать CompilerPass , чтобы собрать все помеченные службы и ввести результат в качестве аргумента вашего контроллера.
Оттуда у вас есть доступ ко всемметоды, которые вам нужны, чтобы найти свои услуги благодаря классу ContainerBuilder (например, с использованием findTaggedServiceIds)

Sylius часто использует этот трюк и даже имеетзаранее сделанный проход компилятора, который делает это (так что вы можете проверить, как это делается внутри) абстрактным способом.
Чтобы использовать его, нам просто нужно создать новый, расширить его и вызвать родительский __construct() справильные параметры.( пример здесь )

Проверьте это там:

https://github.com/diimpp/Sylius/blob/master/src/Sylius/Bundle/ResourceBundle/DependencyInjection/Compiler/PrioritizedCompositeServicePass.php

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...