diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index d3ccfd13a4c..621c674080f 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -306,46 +306,94 @@ For more details, see :doc:`/cookbook/bundles/prepend_extension`, which is specific to the Symfony Framework, but contains more details about this feature. -Creating a Compiler Pass ------------------------- +.. _creating-a-compiler-pass: +.. _components-di-compiler-pass: -You can also create and register your own compiler passes with the container. -To create a compiler pass it needs to implement the -:class:`Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface` -interface. The compiler pass gives you an opportunity to manipulate the -service definitions that have been compiled. This can be very powerful, -but is not something needed in everyday use. +Execute Code During Compilation +------------------------------- -The compiler pass must have the ``process`` method which is passed the container -being compiled:: +You can also execute custom code during compilation by writing your own +compiler pass. By implementing +:class:`Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface` +in your extension, the added ``process()`` method will be called during +compilation:: + // ... use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; - class CustomCompilerPass implements CompilerPassInterface + class AcmeDemoExtension implements ExtensionInterface, CompilerPassInterface { public function process(ContainerBuilder $container) { - // ... + // ... do something during the compilation } + + // ... } +.. versionadded:: 2.8 + Prior to Symfony 2.8, extensions implementing ``CompilerPassInterface`` + were not automatically registered. You needed to register them as explained + in :ref:`the next section `. + +As ``process()`` is called *after* all extensions are loaded, it allows you to +edit service definitions of other extensions as well as retrieving information +about service definitions. + The container's parameters and definitions can be manipulated using the -methods described in the :doc:`/components/dependency_injection/definitions`. -One common thing to do in a compiler pass is to search for all services -that have a certain tag in order to process them in some way or dynamically -plug each into some other service. +methods described in :doc:`/components/dependency_injection/definitions`. + +.. note:: + + Please note that the ``process()`` method in the extension class is + called during the optimization step. You can read + :ref:`the next section ` if you + need to edit the container during another step. + +.. note:: + + As a rule, only work with services definition in a compiler pass and do not + create service instances. In practice, this means using the methods + ``has()``, ``findDefinition()``, ``getDefinition()``, ``setDefinition()``, + etc. instead of ``get()``, ``set()``, etc. + +.. tip:: + + Make sure your compiler pass does not require services to exist. Abort the + method call if some required service is not available. -Registering a Compiler Pass ---------------------------- +A common use-case of compiler passes is to search for all service definitions +that have a certain tag in order to process dynamically plug each into some +other service. See the section on :ref:`service tags ` +for an example. -You need to register your custom pass with the container. Its process method -will then be called when the container is compiled:: +.. _components-di-separate-compiler-passes: + +Creating Separate Compiler Passes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes, you need to do more than one thing during compliation, want to use +compiler passes without an extension or you need to execute some code at +another step in the compilation process. In these cases, you can create a new +class implementing the ``CompilerPassInterface``:: + + use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + use Symfony\Component\DependencyInjection\ContainerBuilder; + + class CustomPass implements CompilerPassInterface + { + public function process(ContainerBuilder $container) + { + // ... do something during the compilation + } + } + +You then need to register your custom pass with the container:: use Symfony\Component\DependencyInjection\ContainerBuilder; $container = new ContainerBuilder(); - $container->addCompilerPass(new CustomCompilerPass); + $container->addCompilerPass(new CustomPass()); .. note:: @@ -354,17 +402,16 @@ will then be called when the container is compiled:: more details. Controlling the Pass Ordering -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +............................. The default compiler passes are grouped into optimization passes and removal passes. The optimization passes run first and include tasks such as resolving references within the definitions. The removal passes perform tasks such -as removing private aliases and unused services. You can choose where in -the order any custom passes you add are run. By default they will be run -before the optimization passes. +as removing private aliases and unused services. When registering compiler +passes using ``addCompilerPass()``, you can configure when your compiler pass +is run. By default, they are run before the optimization passes. -You can use the following constants as the second argument when registering -a pass with the container to control where it goes in the order: +You can use the following constants to determine when your pass is executed: * ``PassConfig::TYPE_BEFORE_OPTIMIZATION`` * ``PassConfig::TYPE_OPTIMIZE`` @@ -373,14 +420,11 @@ a pass with the container to control where it goes in the order: * ``PassConfig::TYPE_AFTER_REMOVING`` For example, to run your custom pass after the default removal passes have -been run:: +been run, use:: - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Compiler\PassConfig; - - $container = new ContainerBuilder(); + // ... $container->addCompilerPass( - new CustomCompilerPass, + new CustomPass(), PassConfig::TYPE_AFTER_REMOVING ); diff --git a/components/dependency_injection/tags.rst b/components/dependency_injection/tags.rst index c92cebaf3f8..2cb5713401a 100644 --- a/components/dependency_injection/tags.rst +++ b/components/dependency_injection/tags.rst @@ -117,11 +117,14 @@ Notice that each was given a tag named ``acme_mailer.transport``. This is the custom tag that you'll use in your compiler pass. The compiler pass is what makes this tag "mean" something. -Create a ``CompilerPass`` -------------------------- +.. _components-di-compiler-pass-tags: +.. _create-a-compilerpass: -Your compiler pass can now ask the container for any services with the -custom tag:: +Create a Compiler Pass +---------------------- + +You can now use a :ref:`compiler pass ` to ask the +container for any services with the ``acme_mailer.transport`` tag:: use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -154,7 +157,7 @@ custom tag:: The ``process()`` method checks for the existence of the ``acme_mailer.transport_chain`` service, then looks for all services tagged ``acme_mailer.transport``. It adds to the definition of the ``acme_mailer.transport_chain`` service a -call to ``addTransport()`` for each "acme_mailer.transport" service it has +call to ``addTransport()`` for each ``acme_mailer.transport`` service it has found. The first argument of each of these calls will be the mailer transport service itself. @@ -175,6 +178,13 @@ run when the container is compiled:: framework. See :doc:`/cookbook/service_container/compiler_passes` for more details. +.. tip:: + + When implementing the ``CompilerPassInterface`` in a service extension, you + do not need to register it. See the + :ref:`components documentation ` for more + information. + Adding Additional Attributes on Tags ------------------------------------ @@ -296,4 +306,3 @@ The double loop may be confusing. This is because a service can have more than one tag. You tag a service twice or more with the ``acme_mailer.transport`` tag. The second foreach loop iterates over the ``acme_mailer.transport`` tags set for the current service and gives you the attributes. - diff --git a/cookbook/service_container/compiler_passes.rst b/cookbook/service_container/compiler_passes.rst index cf629a695ef..d5b7595adc4 100644 --- a/cookbook/service_container/compiler_passes.rst +++ b/cookbook/service_container/compiler_passes.rst @@ -7,25 +7,27 @@ 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:: +can read about how to create them in the components section +":ref:`components-di-compiler-pass`". - // src/Acme/MailerBundle/AcmeMailerBundle.php - namespace Acme\MailerBundle; +When using :ref:`separate compiler passes `, +you need to register them in the ``build()`` method of the bundle class (this +is not needed when implementing the ``process()`` method in the extension):: + + // src/AppBundle/AppBundle.php + namespace AppBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; + use AppBundle\DependencyInjection\Compiler\CustomPass; - use Acme\MailerBundle\DependencyInjection\Compiler\CustomCompilerPass; - - class AcmeMailerBundle extends Bundle + class AppBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); - $container->addCompilerPass(new CustomCompilerPass()); + $container->addCompilerPass(new CustomPass()); } } @@ -33,6 +35,6 @@ One of the most common use-cases of compiler passes is to work with tagged servi (read more about tags 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. For example, if you want to introduce -some sort of "transport" tag in your AcmeMailerBundle, you should call it -``acme_mailer.transport``. +by a dot and finally the "real" name. For example, if you want to introduce +some sort of "mail_transport" tag in your AppBundle, you should call it +``app.mail_transport``.