Las etiquetas son una cadena genérica (junto con algunas opciones) que puedes aplicar a cualquier servicio. Por sí mismas, las etiquetas en realidad no hacen la funcionalidad de tu servicio en modo alguno. Pero si quieres, puedes pedir al constructor del contenedor que consiga una lista de todos los servicios que estén etiquetados con alguna etiqueta específica. Esto es útil en los pases del compilador donde puedes encontrar estos servicios y usarlos o modificarlos de alguna manera específica.
Por ejemplo, si estás usando Swift Mailer te puedes imaginar que quieres implementar una «cadena de transporte», que es una colección de clases que implementan \Swift_Transport. Usando la cadena, querrá que Swift Mailer pruebe varias maneras de transportar el mensaje hasta que una lo consiga.
En primer lugar, define la clase TransportChain:
class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = array();
}
public function addTransport(\Swift_Transport $transport)
{
$this->transports[] = $transport;
}
}
Entonces, define la cadena como un servicio:
parameters:
acme_mailer.transport_chain.class: TransportChain
services:
acme_mailer.transport_chain:
class: "%acme_mailer.transport_chain.class%"
<parameters>
<parameter key="acme_mailer.transport_chain.class">TransportChain</parameter>
</parameters>
<services>
<service id="acme_mailer.transport_chain" class="%acme_mailer.transport_chain.class%" />
</services>
use Symfony\Component\DependencyInjection\Definition;
$container->setParameter('acme_mailer.transport_chain.class', 'TransportChain');
$container->setDefinition('acme_mailer.transport_chain', new Definition('%acme_mailer.transport_chain.class%'));
Ahora, posiblemente quieras que varias de las clases \Swift_Transport sean creadas y añadidas automáticamente a la cadena usando el método addTransport(). Como ejemplo, puedes añadir los siguientes transportes como servicios:
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 }
<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>
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);
Ten en cuenta que a cada uno se le dio una etiqueta llamada acme_mailer.transport. Esta es la etiqueta personalizada que utilizarás en tu pase del compilador. El pase del compilador es lo que da algún «significado» a la etiqueta.
Tu pase del compilador ahora puede pedir al contenedor cualquier servicio con la etiqueta personalizada:
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 => $attributes) {
$definition->addMethodCall(
'addTransport',
array(new Reference($id))
);
}
}
}
El método process() comprueba la existencia del servicio acme_mailer.transport_chain, a continuación, busca todos los servicios etiquetados cómo acme_mailer.transport. Los añade a la definición del servicio acme_mailer.transport_chain llamando a addTransport() por cada servicios "acme_mailer.transport" que haya encontrado. El primer argumento de cada una de estas llamadas será el servicio de transporte de correo en sí mismo.
También necesitas registrar el pase en el contenedor, entonces se ejecutará al compilar el contenedor:
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->addCompilerPass(new TransportCompilerPass);
Nota
Los pases del compilador son registrados de manera diferente si estás usando la pila completa de la plataforma. Ve Cómo trabajan los pases del compilador en los paquetes para más detalles.
Algunas veces necesitarás información adicional acerca de cada servicio que se ha marcado con tu etiqueta. Por ejemplo, posiblemente quieras agregar un alias a cada TransportChain.
Para empezar, cambia la clase TransportChain:
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];
}
else {
return;
}
}
}
Como puedes ver, cuando se llama a addTransport, no sólo necesita un objeto Swift_Transport, sino también una cadena con el alias del transporte. Por lo tanto, ¿cómo podemos permitir que cada servicio etiquetado como transporte también suministre un alias?
Para responder a esto, cambia la declaración del servicio:
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 }
<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>
Ten en cuenta que agregaste una clave genérica alias a la etiqueta. Para utilizarla efectivamente, actualiza el compilador:
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 => $tagAttributes) {
foreach ($tagAttributes as $attributes) {
$definition->addMethodCall(
'addTransport',
array(new Reference($id), $attributes["alias"])
);
}
}
}
}
La parte más complicada es la variable $attributes. Debido a que puedes utilizar la misma etiqueta muchas veces en el mismo servicio (por ejemplo, en teoría, podrías etiquetar el mismo servicio 5 veces con la etiqueta acme_mailer.transport), $attributes es un arreglo con información de la etiqueta de cada una de las etiquetas en ese servicio.