如何操作服务的标签
与网上的博客主题被打上诸如“Symfony”或“PHP”这种标签相类似,配置在容器中的服务也可以打上标签。在容器中,标签即意味着服务应当被用于一个特定目的。看下面的代码:
1 2 3 4 5 6 7 |
# app/config/services.yml
services:
foo.twig.extension:
class: AppBundle\Extension\FooExtension
public: false
tags:
- { name: twig.extension } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="Http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service
id="foo.twig.extension"
class="AppBundle\Extension\FooExtension"
public="false">
<tag name="twig.extension" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 |
// app/config/services.php
use Symfony\Component\DependencyInjection\Definition;
$definition = new Definition('AppBundle\Extension\FooExtension');
$definition->setPublic(false);
$definition->addTag('twig.extension');
$container->setDefinition('foo.twig.extension', $definition); |
twig.extension
标签是一个特殊的tag,TwigBundle会在配置信息(被解析的过程)中用到。通过给服务打上twig.extension
标签,TwigBundle就知道有个foo.twig.extension
服务应该被注册成Twig引擎中的扩展。换言之,Twig将找到所有打了twig.extension
标签的服务,然后自动将它们注册为扩展。
至于标签本身,是作用于Symfony或其他第三方扩展之bundle的“通知方式”,即,你的服务应该被“注册到”或是“以某种特殊方式使用在”那个bundle之中。
Symfony框架核心所用到的全部标签列表,参考注入之标签。那里面的每一种标签对于你的服务来说都有着不同的作用,很多tag还需要附加参数(指的是除了name
之外的parameter)。
创建自定义标签 ¶
标签(tags)就是通常的字符串(也可能带有附加选项)。标签可以应用到任何服务中。标签本身不以任何方式对服务发生作用。但如果你选择使用标签,你可以请求服务容器把所有具有相同标签的服务罗列出来。这在编译器传递(compiler pass)过程中特别有用,因为你可以找到这些服务并对其进行某种方式的修改。
例如:当你使用swift Mailer邮件服务时,你可能需要执行一个“传送链”(transport chain),这是一个类的集合,每个类都实现\Swift _Transport
。通过这个chain,你可以令Swift Mailer尝试多种不同的信息传送方式,直到信息发送成功。
首先,先定义一个TransportChain
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = array();
}
public function addTransport(\Swift_Transport $transport)
{
$this->transports[] = $transport;
}
} |
然后,将chain定义为服务:
1 2 3 |
services:
acme_mailer.transport_chain:
class: TransportChain |
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme_mailer.transport_chain" class="TransportChain" />
</services>
</container> |
1 |
$container->register('acme_mailer.transport_chain', 'TransportChain'); |
用自定义标签定义服务 ¶
现在你需要将若干\Swift_Transport
类进行实例化,然后添加到chain中,这个过程是通过addTransport()
方法自动完成的。举例来说,你应该先把以下的transports设为服务:
1 2 3 4 5 6 7 8 9 10 11 |
services:
acme_mailer.transport.smtp:
class: \Swift_SmtpTransport
arguments:
- '%mailer_host%'
tags:
- { name: acme_mailer.transport }
acme_mailer.transport.sendmail:
class: \Swift_SendmailTransport
tags:
- { name: acme_mailer.transport } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme_mailer.transport.smtp" class="\Swift_SmtpTransport">
<argument>%mailer_host%</argument>
<tag name="acme_mailer.transport" />
</service>
<service id="acme_mailer.transport.sendmail" class="\Swift_SendmailTransport">
<tag name="acme_mailer.transport" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 9 |
use Symfony\Component\DependencyInjection\Definition;
$definitionSmtp = new Definition('\Swift_SmtpTransport', array('%mailer_host%'));
$definitionSmtp->addTag('acme_mailer.transport');
$container->setDefinition('acme_mailer.transport.smtp', $definitionSmtp);
$definitionSendmail = new Definition('\Swift_SendmailTransport');
$definitionSendmail->addTag('acme_mailer.transport');
$container->setDefinition('acme_mailer.transport.sendmail', $definitionSendmail); |
注意上面两个服务,每个都有一个名为acme_mailer.transport
的标签。这是个自定义的标签,你可以使用在compiler pass中。Compiler Pass就是使这个标签变得“有意义”的东西。
编写Compiler Pass ¶
现在你可以使用一个compiler pass来请求服务容器提供指定标签(acme_mailer.transport
)的服务了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class TransportCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('acme_mailer.transport_chain')) {
return;
}
$definition = $container->findDefinition(
'acme_mailer.transport_chain'
);
$taggedServices = $container->findTaggedServiceIds(
'acme_mailer.transport'
);
foreach ($taggedServices as $id => $tags) {
$definition->addMethodCall(
'addTransport',
array(new Reference($id))
);
}
}
} |
process()
方法先是用来检查acme_mailer.transport_chain
服务是否存在,然后寻找所有被打上acme_mailer.transport
标签的服务。接下来,这些服务被添加到acme_mailer.transport_chain
的服务定义中,每一个被找到的服务都是通过调用定义中的addTransport()
方法的来完成添加的。每次调用该方法时,第一个参数即是mailer transport服务本身。
将Pass注册给Container ¶
完成编译器传递之后,你需要注册这个传递给服务容器。然后,容器在被编译的时候,完成传递的动作:
1 2 3 4 |
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->addCompilerPass(new TransportCompilerPass()); |
如果你使用完整版框架,编译器传递将会有不同的注册方式。参考 如何在Bundles中对Compiler Passes进行操作。
当在一个服务扩展中实现CompilerPassInterface
接口时,毋须再注册pass。参考中文DI组件文档 在编译过程中执行代码。
对标签添加附加属性 ¶
有些时候你需要得到每一个“带标签”服务的附加信息。接上例,你可能需要给每一个transport chain传送链中的成员添加一个假名(alias)。
修改TransportChain
类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = array();
}
public function addTransport(\Swift_Transport $transport, $alias)
{
$this->transports[$alias] = $transport;
}
public function getTransport($alias)
{
if (array_key_exists($alias, $this->transports)) {
return $this->transports[$alias];
}
}
} |
如你所见,当addTransport方法被调用时,参数中不仅有Swift_Transport
对象,还有一个transport假名的字符串。那么,如何才能让每个打了标签的transport服务也应用相应的假名呢?
要回答这个问题,先修改服务定义如下:
1 2 3 4 5 6 7 8 9 10 11 |
services:
acme_mailer.transport.smtp:
class: \Swift_SmtpTransport
arguments:
- '%mailer_host%'
tags:
- { name: acme_mailer.transport, alias: foo }
acme_mailer.transport.sendmail:
class: \Swift_SendmailTransport
tags:
- { name: acme_mailer.transport, alias: bar } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="acme_mailer.transport.smtp" class="\Swift_SmtpTransport">
<argument>%mailer_host%</argument>
<tag name="acme_mailer.transport" alias="foo" />
</service>
<service id="acme_mailer.transport.sendmail" class="\Swift_SendmailTransport">
<tag name="acme_mailer.transport" alias="bar" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 9 |
use Symfony\Component\DependencyInjection\Definition;
$definitionSmtp = new Definition('\Swift_SmtpTransport', array('%mailer_host%'));
$definitionSmtp->addTag('acme_mailer.transport', array('alias' => 'foo'));
$container->setDefinition('acme_mailer.transport.smtp', $definitionSmtp);
$definitionSendmail = new Definition('\Swift_SendmailTransport');
$definitionSendmail->addTag('acme_mailer.transport', array('alias' => 'bar'));
$container->setDefinition('acme_mailer.transport.sendmail', $definitionSendmail); |
注意你刚才添加了通用的alias
属性到标签中。为了真正用到它,还需要更改compiler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class TransportCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('acme_mailer.transport_chain')) {
return;
}
$definition = $container->getDefinition(
'acme_mailer.transport_chain'
);
$taggedServices = $container->findTaggedServiceIds(
'acme_mailer.transport'
);
foreach ($taggedServices as $id => $tags) {
foreach ($tags as $attributes) {
$definition->addMethodCall(
'addTransport',
array(new Reference($id), $attributes["alias"])
);
}
}
}
} |
双循环可能略显疑惑,这是因为一个服务可以有多个标签。之前你对两个服务(或更多)打上了acme_mailer.transport
标签。第二重循环只是在遍历当前服务的acme_mailer.transport
标签本身,你得到的是属性数组。