Skip to content

Commit

Permalink
feature symfony#5920 Document automatic registration of extension com…
Browse files Browse the repository at this point in the history
…piler passes (WouterJ)

This PR was squashed before being merged into the 2.8 branch (closes symfony#5920).

Discussion
----------

Document automatic registration of extension compiler passes

| Q | A
| --- | ---
| Doc fix? | no
| New docs? | yes (symfony/symfony#13761)
| Applies to | 2.8+
| Fixed tickets | -

Commits
-------

353df25 Document automatic registration of extension compiler passes
  • Loading branch information
wouterj committed Feb 6, 2016
2 parents 41d43dd + 353df25 commit 075a81d
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 52 deletions.
112 changes: 78 additions & 34 deletions components/dependency_injection/compilation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <components-di-separate-compiler-passes>`.

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 <components-di-separate-compiler-passes>` 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 <components-di-compiler-pass-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::

Expand All @@ -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``
Expand All @@ -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
);

Expand Down
21 changes: 15 additions & 6 deletions components/dependency_injection/tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <components-di-separate-compiler-passes>` to ask the
container for any services with the ``acme_mailer.transport`` tag::

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
Expand Down Expand Up @@ -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.

Expand All @@ -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 <components-di-compiler-pass>` for more
information.

Adding Additional Attributes on Tags
------------------------------------

Expand Down Expand Up @@ -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.

26 changes: 14 additions & 12 deletions cookbook/service_container/compiler_passes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,34 @@ 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 <components-di-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());
}
}

One of the most common use-cases of compiler passes is to work with tagged services
(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``.

0 comments on commit 075a81d

Please sign in to comment.