Symfony 5.4+中如何获取注入服务实例的对应标签?
问题描述
环境:Symfony 5.4+,PHP 8+
我有一个EventService类,构造函数接收EventInterface类型的参数:
<?php class EventService { public function __construct(private EventInterface $event) { } public function execute() { // 需要获取$event对应的标签 } }
有多个实现EventInterface的事件类,比如这个示例:
<?php class Event implements EventInterface { // 这里没有tags属性 }
我在service.yaml里给这个Event服务加了标签:
Event: public: true tags: - { name: 'foo', priority: 20 } - { name: 'bar', priority: -10 }
请问怎么在EventService里拿到$event对应的"foo"和"bar"标签?
我试过用ContainerBuilder,但报错“service not found”,代码是这样的:
$containerBuilder->findDefinitions($event::class)->getTags()
解决方案
你用ContainerBuilder报错是因为它是容器编译阶段的工具,运行时的容器(ContainerInterface)不会保留服务定义的标签信息,直接调用肯定找不到。下面给两个可行的解决办法:
方案一:用编译器传递把标签注入Event类
这是最直接的方式,通过编译器传递在容器编译时把标签信息注入到对应的Event实例里:
- 先写一个编译器传递类,遍历带目标标签的Event服务,给它们添加注入标签的方法调用:
<?php namespace App\DependencyInjection\Compiler; use App\Event\EventInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class EventTagPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { // 收集所有带foo或bar标签的服务 $taggedServices = array_merge( $container->findTaggedServiceIds('foo'), $container->findTaggedServiceIds('bar') ); foreach ($taggedServices as $serviceId => $tags) { $definition = $container->getDefinition($serviceId); $class = $definition->getClass(); // 只处理实现EventInterface的服务 if (!is_subclass_of($class, EventInterface::class)) { continue; } // 给服务添加setTags方法调用,注入标签数组 $definition->addMethodCall('setTags', [$tags]); } } }
- 给
EventInterface和实现类添加setTags和getTags方法:
<?php interface EventInterface { public function setTags(array $tags): void; public function getTags(): array; } class Event implements EventInterface { private array $tags = []; public function setTags(array $tags): void { $this->tags = $tags; } public function getTags(): array { return $this->tags; } }
- 在
Kernel.php里注册这个编译器传递:
<?php namespace App; use App\DependencyInjection\Compiler\EventTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel as BaseKernel; class Kernel extends BaseKernel { // ... protected function build(ContainerBuilder $container) { $container->addCompilerPass(new EventTagPass()); } }
- 之后在
EventService里直接调用$event->getTags()就能拿到标签了:
public function execute() { $tags = $this->event->getTags(); // 这里处理标签逻辑 }
方案二:结合服务定位器和编译器传递
如果不想修改Event类,可以用服务定位器,但还是需要编译器传递提前收集标签信息:
- 先在
service.yaml里给所有Event服务加一个统一标签,再定义服务定位器:
services: App\Service\EventService: arguments: $event: '@Event' $eventTagMap: '@app.event_tag_map' # 这个是我们要定义的标签映射服务 # 给Event服务加统一标签 Event: public: true tags: - { name: 'foo', priority: 20 } - { name: 'bar', priority: -10 } - { name: 'app.event' }
- 写编译器传递创建标签映射服务:
<?php namespace App\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; class EventTagMapPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $taggedServices = $container->findTaggedServiceIds('app.event'); $tagMap = []; foreach ($taggedServices as $serviceId => $_) { $definition = $container->getDefinition($serviceId); $class = $definition->getClass(); $tagMap[$class] = $definition->getTags(); } // 创建一个参数保存标签映射 $container->setParameter('app.event_tags', $tagMap); // 或者直接定义一个服务来持有标签映射 $mapDefinition = new Definition(\ArrayObject::class, [$tagMap]); $container->setDefinition('app.event_tag_map', $mapDefinition); } }
- 在
Kernel.php注册这个编译器传递,然后修改EventService注入标签映射:
<?php class EventService { public function __construct( private EventInterface $event, private \ArrayObject $eventTagMap ) { } public function execute() { $eventClass = $this->event::class; $tags = $this->eventTagMap[$eventClass] ?? []; // 处理标签逻辑 } }
提示:方案一更简单直接,适合大多数场景;方案二更适合不需要修改Event类的情况,但需要额外维护标签映射。
内容的提问来源于stack exchange,提问作者Hevyweb
相关产品推荐
相关产品推荐

