berny / tag-bundle
帮助收集带有标签的服务并将它们注入其他服务
Requires
- symfony/config: >=2.0.10
- symfony/dependency-injection: >=2.0.10
- symfony/http-kernel: >=2.0.10
Requires (Dev)
- php: >=5.5
- phpunit/phpunit: ^4.5
README
你是否厌倦了只为了收集容器上标记的服务而添加 CompilerPass
?
使用这个包说NO!
⚠ 注意 ⚠
注意命名空间的变化
- 在
0.4.0
之前:Berny\Bundle\TagBundle
- 在
0.4.0
之后:xPheRe\Bundle\TagBundle
为什么要这样做呢?
很多时候,你想要搜索带有特定标记的服务,并用它们调用你的服务中的某个方法。这可以通过自定义 CompilerPass
来完成。
services: my_plugin_enumerator: class: PluginEnumerator useless_plugin: class: UselessPlugin tag: - { name: my_plugin } even_more_useless_plugin: class: EvenMoreUselessPlugin tag: - { name: my_plugin }
class PluginEnumeratorConsumerCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->has('my_plugin_enumerator')) { return; } $definition = $container->findDefinition('my_plugin_enumerator'); $taggedServices = $container->findTaggedServices('my_plugin'); foreach ($taggedServices as $id => $attributes) { $definition->addMethodCall('addPlugin', array(new Reference($id))); } } }
另一个用例是将服务注入到所有带有特定标记的其他服务中。一个例子
services: my_event_dispatcher: class: MyEventDispatcher useless_service: class: UselessService tag: - { name: my_event_dispatcher.aware } even_more_useless_service: class: EvenMoreUselessService tag: - { name: my_event_dispatcher.aware }
class MyEventDispatcherInjectableCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->has('my_event_dispatcher')) { return; } $reference = new Reference('my_event_dispatcher'); $taggedServices = $container->findTaggedServices('my_event_dispatcher.aware'); foreach ($taggedServices as $id => $attributes) { $definition = $container->findDefinition($id); $definition->addMethodCall('setMyEventDispatcher', array($reference)); } } }
这个样板代码在每个我见过的项目中一次又一次地重复。使用这个包,你可以告别大多数这种编译器传递。
特性
使用这个包,你可以
- 将一个服务标记为另一个标签的消费者。
- 将一个服务标记为可注入到其他服务中。
兼容性
在Symfony2下测试,从2.0.10到2.6.3
安装
从 composer/packagist
- 在composer中需要
xphere/tag-bundle
包 - 将包添加到你的
AppKernel.php
使用
消费者
你可以定义一个服务作为另一个标签的“标签消费者”,并让包建立它们之间的关系。只需将你的服务标记为 tag.consumer
,并指定要收集哪个 tag
以及要调用哪个 method
。
使用此包的第一个例子只是配置
services: my_plugin_enumerator: class: PluginEnumerator tags: - { name: tag.consumer, tag: my_plugin, method: addPlugin } useless_plugin: class: UselessPlugin tag: - { name: my_plugin } even_more_useless_plugin: class: EvenMoreUselessPlugin tag: - { name: my_plugin }
唯一的更改是 my_plugin_enumerator
中的标签。编译器传递的样板代码消失了。
这会对每个 my_plugin
调用 PluginEnumerator::addPlugin
,但你也可以通过 bulk
参数一次性调用所有服务。
services: my_plugin_enumerator: class: PluginEnumerator tags: - { name: tag.consumer, tag: my_plugin, method: addPlugins, bulk: true }
这仅通过一个数组调用 PluginEnumerator::addPlugins
,包含服务。
要使服务通过其构造函数通过它消费其依赖项,只需省略标签中的 method
属性。
可注入
你可以定义一个服务作为来自另一个标签的“标签可注入”服务,并让包做艰苦的工作。只需将你的服务标记为 tag.injectable
,并指定要收集哪个 tag
以及每个服务中要调用哪个 method
。
介绍中的第二个例子将像这样
services: my_event_dispatcher: class: MyEventDispatcher tag: - { name: tag.injectable, tag: my_event_dispatcher.aware, method: setMyEventDispatcher } useless_service: class: UselessService tag: - { name: my_event_dispatcher.aware } even_more_useless_service: class: EvenMoreUselessService tag: - { name: my_event_dispatcher.aware }
唯一的更改是 my_event_dispatcher
中的标签。编译器传递的样板代码也消失了。
这迫使所有 my_event_dispatcher.aware
都有一个 setMyEventDispatcher
方法。但你可以通过 method
参数为特定服务更改它。
services: my_event_dispatcher: class: MyEventDispatcher tag: - { name: tag.injectable, tag: my_event_dispatcher.aware, method: setMyEventDispatcher } useless_service: class: UselessService tag: - { name: my_event_dispatcher.aware, method: setEventDispatcher } even_more_useless_service: class: EvenMoreUselessService tag: - { name: my_event_dispatcher.aware }
现在它正在为 UselessService
调用 setEventDispatcher
,而对于其他服务则是默认方法。
高级使用
这些都是基础知识,虽然还有更多选项可供您对依赖项进行主要控制。
顺序
您可以使用每个标签中的 order
字段指定服务将按什么顺序注入到消费者中。较低的顺序优先于较高的顺序。未指定顺序的标记服务将在已排序服务之后注入。如果顺序之间有冲突,则保持symfony声明顺序。
索引批量服务
当批量活动时,您可以使用 key
指定用于索引每个标签的值,而不是使用纯数组。
services: my_command_bus: class: MyCommandBus tags: - { name: tag.consumer, tag: my_command_handler, bulk: true, key: handles } my_class_command_handler: class: MyClassCommandHandler tag: - { name: my_command_handler, handles: MyClass } other_class_command_handler: class: OtherClassCommandHandler tag: - { name: my_command_handler, handles: OtherClass }
这会导致以下注入
[ 'MyClass' => new MyClassCommandHandler(), 'OtherClass' => new OtherClassCommandHandler(), ]
您也可以在消费者定义中使用multiple
字段指定多个元素将具有相同的索引,并且需要收集数组而不是单个服务。
services: my_event_bus: class: MyEventBus tags: - { name: tag.consumer, tag: my_event_handler, bulk: true, key: listensTo, multiple: true } first_event_handler: class: FirstEventHandler tag: - { name: my_event_handler, listensTo: MyEvent } second_event_handler: class: SecondEventHandler tag: - { name: my_event_handler, listensTo: OtherEvent } third_event_handler: class: ThirdEventHandler tag: - { name: my_event_handler, listensTo: MyEvent }
这会导致以下注入
[ 'MyEvent' => [ new FirstEventHandler(), new ThirdEventHandler(), ], 'OtherEvent' => [ new SecondEventHandler(), ], ]
如果指定了,Multiple也会尊重排序。
参考
通常,依赖项会直接注入到您的服务中,但您可以通过在消费者定义中将字段reference
设置为false
,将依赖项作为服务ID注入。
instanceof
您可以通过在消费者定义中使用字段instanceof
强制依赖项成为某个类或接口的实例。
无包
您可以在不添加“整个”包到您的Kernel
的情况下手动添加TagConsumerPass
或TagInjectablePass
(或两者),甚至可以自定义用于应用它们的标签名称。
在您的Kernel
中
[...] use xPheRe\Bundle\TagBundle\DependencyInjection\Compiler\TagConsumerPass; use xPheRe\Bundle\TagBundle\DependencyInjection\Compiler\TagInjectablePass; [...] class AppKernel extends Kernel { [...] protected function prepareContainer(ContainerBuilder $container) { parent::prepareContainer($container); $container->addCompilerPass(new TagConsumerPass('tag_collector')); $container->addCompilerPass(new TagInjectablePass('tag_injectable')); } [...] }