bangpound / callable-compiler-pass

Define a compiler pass with a callable instead of a class.
6 stars 1 forks source link

Callable Compiler Pass Build Status

Use this implementation of CompilerPassInterface to create compiler passes in Symfony without defining another class.

Usage

You should use this class wherever you instantiate a container compiler pass to modify the Symfony dependency injection container. For typical Symfony applications, this happens in a bundle.

Without CallableCompilerPass, your bundle class would add the compiler pass this way:

<?php
namespace My\Bundle\CoolBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use My\Bundle\CoolBundle\DependencyInjection\Compiler\AddMyStuffPass;

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

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

With CallableCompilerPass, you can skip creating the AddMyTaggedServicesPass and define the compiler pass in the bundle's build method:

<?php
namespace My\Bundle\CoolBundle;

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

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

        $container->addCompilerPass(new CallableCompilerPass(
            function (ContainerBuilder $container) {
                if (!$container->has('my_cool.service')) {
                    return;
                }

                $definition = $container->findDefinition('my_cool.service');

                $taggedServices = $container->findTaggedServiceIds(
                    'my_cool.service_addition'
                );

                foreach ($taggedServices as $id => $tags) {
                    $definition->addMethodCall(
                        'addThing', array(new Reference($id))
                    );
                }
            }
        ));
    }
}

The utility of the CallableCompilerPass is most apparent when developing a Symfony application with the new MicroKernelTrait in Symfony 2.8 and 3.0.

<?php

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;

class AppKernel extends Kernel
{
    use MicroKernelTrait;

    public function registerBundles()
    {
        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
        );

        return $bundles;
    }

    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
    {
        $c->addCompilerPass(new CallableCompilerPass(function (ContainerBuilder $container) {
            if (!$container->has('my_cool.service')) {
                return;
            }

            $definition = $container->findDefinition('my_cool.service');

            $taggedServices = $container->findTaggedServiceIds(
                'my_cool.service_addition'
            );

            foreach ($taggedServices as $id => $tags) {
                $definition->addMethodCall(
                    'addThing', array(new Reference($id))
                );
            }
        });
    }
}

In the microkernel, you might also use CallableCompilerPass to remove services you do not need but which are added by the Symfony FrameworkBundle by default, for example.