php - 如何注入一组实现相同接口的服务,而不声明每个服务的接线?
问题描述
我正在开发一个应用程序,其中有一些处理程序作为我希望能够调用的服务。他们都实现了一个ItemHandlerInterface
.
我希望能够在控制器中检索所有ItemHandlerInterface
服务集合,而无需手动连接它们。
到目前为止,我专门标记了它们:
服务.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
给我的控制器构造函数。但这很丑陋,我想找到一种更优雅的方式。
项目更新控制器
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
}
解决方案
您可以一口气注入所有标记的服务,而无需使用编译器通道。
配置
由于您已经在进行标记,如问题所示,只需声明注入即可:
_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
将被注入您的服务里面。您可以简单地对其进行迭代以获取其中的每一个。
额外的
从 4.3 开始,您可以为此使用标记的服务定位器。配置同样简单,但您可以获得能够延迟实例化服务的优势,而不必从一开始就实例化所有服务。
你可以在这里阅读更多。
推荐阅读
- apache-spark - Azure HD Insight - YARN UI 突然没有在 stderr 上显示日志
- javascript - 使用 jsp 页面进行身份验证的 HTML 重定向
- python - 在 KivyMD 上的小部件之间添加数值
- java - 尝试使用此处广泛提倡的方法在非活动方法中执行 getResources 时“无法解析方法 getResources”
- rust - 获取一周的开始/结束日期
- node.js - 在 SSL 上使用 BinaryJS 会引发安全异常
- google-chrome - 是否可以在浏览器中冻结 DOM 以进行拖动调试?
- vb.net - 如何在 Oracle 12c 的 VB.NET 应用程序中包含 Oracle 客户端?
- telegram - Telegram 从哪里获取我的网站链接的缩略图?
- html - 使聊天消息不重叠 CSS