首页 > 解决方案 > 如何注入一组实现相同接口的服务,而不声明每个服务的接线?

问题描述

我正在开发一个应用程序,其中有一些处理程序作为我希望能够调用的服务。他们都实现了一个ItemHandlerInterface.

我希望能够在控制器中检索所有ItemHandlerInterface服务集合,而无需手动连接它们。

到目前为止,我专门标记了它们:

服务.yaml

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

并尝试在控制器中检索我的服务集合。如果只有一个服务实现ItemHandlerInterface,它就可以工作,但是一旦我创建了几个(如下所示TestHandlerTest2Handler,我最终得到一个The service "service_locator.03wqafw.App\Controller\ItemUpdateController" has a dependency on a non-existent service "App\Model\ItemHandlerInterface".

如何动态检索实现我的接口的所有服务?

一种肮脏的解决方案是强制所有ItemHandlerInterfacepublic: true传递Container给我的控制器构造函数。但这很丑陋,我想找到一种更优雅的方式。

项目更新控制器

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
}

标签: phpsymfonydependency-injection

解决方案


您可以一口气注入所有标记的服务,而无需使用编译器通道。

配置

由于您已经在进行标记,如问题所示,只需声明注入即可:

_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;
}

在您的类中,aRewindableGenerator将被注入您的服务里面。您可以简单地对其进行迭代以获取其中的每一个。

从 3.4开始可用;它仍然受支持


额外的

从 4.3 开始,您可以为此使用标记的服务定位器。配置同样简单,但您可以获得能够延迟实例化服务的优势,而不必从一开始就实例化所有服务。

你可以在这里阅读更多。


推荐阅读