From ecbef63c45d10e357794409dd6c3f8402d003d3e Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Fri, 25 May 2012 11:14:25 +0100 Subject: [PATCH 1/4] Moved and edited the tags article to fit it into the component section --- components/dependency_injection/index.rst | 1 + .../dependency_injection}/tags.rst | 125 ++++++------------ redirection_map | 1 + 3 files changed, 43 insertions(+), 84 deletions(-) rename {cookbook/service_container => components/dependency_injection}/tags.rst (54%) diff --git a/components/dependency_injection/index.rst b/components/dependency_injection/index.rst index ac0e9d2a7da..20e26e0263e 100644 --- a/components/dependency_injection/index.rst +++ b/components/dependency_injection/index.rst @@ -7,6 +7,7 @@ introduction definitions compilation + tags factories parentservices diff --git a/cookbook/service_container/tags.rst b/components/dependency_injection/tags.rst similarity index 54% rename from cookbook/service_container/tags.rst rename to components/dependency_injection/tags.rst index 510728bc6a0..fb988546525 100644 --- a/cookbook/service_container/tags.rst +++ b/components/dependency_injection/tags.rst @@ -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 +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; @@ -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 - - - Acme\MailerBundle\TransportChain + TransportChain - + - + .. 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 @@ -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 @@ -92,67 +87,42 @@ As an example we add the following transports as services: class: \Swift_SendmailTransport tags: - { name: acme_mailer.transport } - + .. code-block:: xml - %mailer_host% - + - + .. 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) @@ -160,9 +130,9 @@ method. The ``TransportCompilerPass`` should look like this:: 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))); } @@ -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); diff --git a/redirection_map b/redirection_map index 814b30c3eca..3e979ddb236 100644 --- a/redirection_map +++ b/redirection_map @@ -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 From fdaf8bb4b4655dfbddc9f3074f7dd64164fa0863 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Fri, 25 May 2012 12:02:55 +0100 Subject: [PATCH 2/4] Added cookbook on differences whne using compiler passes in the framework --- .../service_container/compiler_passes.rst | 32 +++++++++++++++++++ cookbook/service_container/index.rst | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 cookbook/service_container/compiler_passes.rst diff --git a/cookbook/service_container/compiler_passes.rst b/cookbook/service_container/compiler_passes.rst new file mode 100644 index 00000000000..7182d1953d9 --- /dev/null +++ b/cookbook/service_container/compiler_passes.rst @@ -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``. diff --git a/cookbook/service_container/index.rst b/cookbook/service_container/index.rst index 3b0a82ad06e..be8ad17868b 100644 --- a/cookbook/service_container/index.rst +++ b/cookbook/service_container/index.rst @@ -6,4 +6,4 @@ Service Container event_listener scopes - tags + compiler_passes From 359ffd9790b4945d30e101027f9da1aef8c82406 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Fri, 25 May 2012 12:04:24 +0100 Subject: [PATCH 3/4] Updated the map to reflect changes --- cookbook/map.rst.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index 64c9d03358b..0bc2001bb88 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -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` From 83636b7cf048bdd86cf63c361c26e8b3afebe335 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Fri, 25 May 2012 14:39:53 +0100 Subject: [PATCH 4/4] Clarified that it is container builders that can work with tags --- components/dependency_injection/tags.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/dependency_injection/tags.rst b/components/dependency_injection/tags.rst index fb988546525..708757f0797 100644 --- a/components/dependency_injection/tags.rst +++ b/components/dependency_injection/tags.rst @@ -1,10 +1,10 @@ -.. index:: +.. index:: single: Service Container; Tags How to make your Services use Tags ================================== -You can ask the container for any services that were tagged when registered +You can ask a container builder for any services that were tagged when registered 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,