Skip to content

Moved and updated the tags article from cookbook to components section #1398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 25, 2012
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/dependency_injection/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
introduction
definitions
compilation
tags
factories
parentservices

Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@
How to make your Services use Tags
==================================

Several of Symfony2's core services depend on tags to recognize which services
should be loaded, notified of events, or handled in some other special way.
For example, Twig uses the tag ``twig.extension`` to load extra extensions.

But you can also use tags in your own bundles. For example in case your service
handles a collection of some kind, or implements a "chain", in which several alternative
strategies are tried until one of them is successful. In this article I will use the example
You can ask the container for any services that were tagged when registered
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only the ContainerBuilder, not the Container

with it. This is useful in compiler passes where you can find services which
were registered in config files you do not have control over and make use
of them. This is useful if your service handles a collection of some kind,
or implements a "chain", in which several alternative strategies are tried
until one of them is successful. You can then allow services to be registered
with a tag and add these services to your collection or chain.

For example, if you are using Swift Mailer and want to implement a
of a "transport chain", which is a collection of classes implementing ``\Swift_Transport``.
Using the chain, the Swift mailer may try several ways of transport, until one succeeds.
This post focuses mainly on the dependency injection part of the story.
Using the chain, Swift Mailer can try several ways of transport, until one
succeeds.

To begin with, define the ``TransportChain`` class::

namespace Acme\MailerBundle;

class TransportChain
{
private $transports;

public function __construct()
{
$this->transports = array();
}

public function addTransport(\Swift_Transport $transport)
{
$this->transports[] = $transport;
Expand All @@ -40,33 +40,29 @@ Then, define the chain as a service:

.. code-block:: yaml

# src/Acme/MailerBundle/Resources/config/services.yml
parameters:
acme_mailer.transport_chain.class: Acme\MailerBundle\TransportChain
acme_mailer.transport_chain.class: TransportChain

services:
acme_mailer.transport_chain:
class: %acme_mailer.transport_chain.class%

.. code-block:: xml

<!-- src/Acme/MailerBundle/Resources/config/services.xml -->

<parameters>
<parameter key="acme_mailer.transport_chain.class">Acme\MailerBundle\TransportChain</parameter>
<parameter key="acme_mailer.transport_chain.class">TransportChain</parameter>
</parameters>

<services>
<service id="acme_mailer.transport_chain" class="%acme_mailer.transport_chain.class%" />
</services>

.. code-block:: php

// src/Acme/MailerBundle/Resources/config/services.php

use Symfony\Component\DependencyInjection\Definition;
$container->setParameter('acme_mailer.transport_chain.class', 'Acme\MailerBundle\TransportChain');

$container->setParameter('acme_mailer.transport_chain.class', 'TransportChain');

$container->setDefinition('acme_mailer.transport_chain', new Definition('%acme_mailer.transport_chain.class%'));

Define Services with a Custom Tag
Expand All @@ -80,7 +76,6 @@ As an example we add the following transports as services:

.. code-block:: yaml

# src/Acme/MailerBundle/Resources/config/services.yml
services:
acme_mailer.transport.smtp:
class: \Swift_SmtpTransport
Expand All @@ -92,77 +87,52 @@ As an example we add the following transports as services:
class: \Swift_SendmailTransport
tags:
- { name: acme_mailer.transport }

.. code-block:: xml

<!-- src/Acme/MailerBundle/Resources/config/services.xml -->
<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>

.. code-block:: php

// src/Acme/MailerBundle/Resources/config/services.php

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);

Notice the tags named "acme_mailer.transport". We want the bundle to recognize
these transports and add them to the chain all by itself. In order to achieve
this, we need to add a ``build()`` method to the ``AcmeMailerBundle`` class::

namespace Acme\MailerBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

use Acme\MailerBundle\DependencyInjection\Compiler\TransportCompilerPass;

class AcmeMailerBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new TransportCompilerPass());
}
}
Notice the tags named "acme_mailer.transport". This is the custom tag to use ion your compiler pass::

Create a ``CompilerPass``
-------------------------

You will have spotted a reference to the not yet existing ``TransportCompilerPass`` class.
This class will make sure that all services with a tag ``acme_mailer.transport``
will be added to the ``TransportChain`` class by calling the ``addTransport()``
method. The ``TransportCompilerPass`` should look like this::
Your compiler pass can then ask the container for any services with the
custom tag::

namespace Acme\MailerBundle\DependencyInjection\Compiler;

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 (false === $container->hasDefinition('acme_mailer.transport_chain')) {
return;
}

$definition = $container->getDefinition('acme_mailer.transport_chain');

foreach ($container->findTaggedServiceIds('acme_mailer.transport') as $id => $attributes) {
$definition->addMethodCall('addTransport', array(new Reference($id)));
}
Expand All @@ -176,26 +146,13 @@ to the definition of the ``acme_mailer.transport_chain`` service a call to
The first argument of each of these calls will be the mailer transport service
itself.

.. note::

By convention, tag names consist of the name of the bundle (lowercase,
underscores as separators), followed by a dot, and finally the "real"
name, so the tag "transport" in the AcmeMailerBundle should be: ``acme_mailer.transport``.
Register the pass with the container
------------------------------------

The Compiled Service Definition
-------------------------------
You also need to register the pass with the container, it will then be
run when the container is compiled.

Adding the compiler pass will result in the automatic generation of the following
lines of code in the compiled service container. In case you are working
in the "dev" environment, open the file ``/cache/dev/appDevDebugProjectContainer.php``
and look for the method ``getTransportChainService()``. It should look like this::

protected function getAcmeMailer_TransportChainService()
{
$this->services['acme_mailer.transport_chain'] = $instance = new \Acme\MailerBundle\TransportChain();

$instance->addTransport($this->get('acme_mailer.transport.smtp'));
$instance->addTransport($this->get('acme_mailer.transport.sendmail'));
use Symfony\Component\DependencyInjection\ContainerBuilder;

return $instance;
}
$container = new ContainerBuilder();
$container->addCompilerPass(new TransportCompilerPass);
2 changes: 1 addition & 1 deletion cookbook/map.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@

* :doc:`/cookbook/service_container/event_listener`
* :doc:`/cookbook/service_container/scopes`
* :doc:`/cookbook/service_container/tags`
* :doc:`/cookbook/service_container/compiler_passes`

* :doc:`/cookbook/security/index`

Expand Down
32 changes: 32 additions & 0 deletions cookbook/service_container/compiler_passes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
How to work with Compiler Passes in Bundles
===========================================

Compiler passes give you an opportunity to manipulate other service
definitions that have been registered with the service container. You
can read about how to create them in the components section ":doc:`/components/dependency_injection/compilation`".
To register a compiler pass from a bundle you need to add it to the build
method of the bundle definition class::

namespace Acme\MailerBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

use Acme\MailerBundle\DependencyInjection\Compiler\CustomCompilerPass;

class AcmeMailerBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new CustomCompilerPass());
}
}

One of the common tasks for compiler passes is to work with tagged services,
read more about this in the components section ":doc:`/components/dependency_injection/tags`".
If you are using custom tags in a bundle then by convention, tag names consist
of the name of the bundle (lowercase, underscores as separators), followed
by a dot, and finally the "real" name, so the tag "transport" in the AcmeMailerBundle
should be: ``acme_mailer.transport``.
2 changes: 1 addition & 1 deletion cookbook/service_container/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Service Container

event_listener
scopes
tags
compiler_passes
1 change: 1 addition & 0 deletions redirection_map
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/cookbook/tools/finder /components/finder
/cookbook/service_container/parentservices /components/dependency_injection/parentservices
/cookbook/service_container/factories /components/dependency_injection/factories
/cookbook/service_container/tags /components/dependency_injection/tags
/reference/configuration/mongodb /bundles/DoctrineMongoDBBundle/config
/reference/YAML /components/yaml
/components/dependency_injection /components/dependency_injection/introduction
Expand Down