Reference / Symfony
/

Compiler Passes

Hooks that modify the service container during the compile step. Used to wire up tagged services, swap implementations, validate config.

Advanced
  • Implement `CompilerPassInterface` and register in your bundle's `build()` method, or in `Kernel::build()` for app-level passes.
  • Most common use: collect every service tagged `app.thing` and inject them into a registry service.
  • `$container->findTaggedServiceIds('tag.name')` is the workhorse method.
  • Run only at cache warmup — zero runtime cost. Errors here surface as ContainerException.
  • Built-in passes (e.g. ResolveTaggedIteratorArgumentPass) are why `!tagged_iterator` works in YAML.
  • Modern Symfony often replaces a custom pass with `#[AutoconfigureTag]` + `#[AutowireIterator]` — simpler and still type-safe.
class HandlerCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $c): void
    {
        $registry = $c->getDefinition(HandlerRegistry::class);
        foreach ($c->findTaggedServiceIds('app.handler') as $id => $tags) {
            $registry->addMethodCall('register', [new Reference($id)]);
        }
    }
}

Common gotchas

  • Compiler pass order matters — `PassConfig::TYPE_BEFORE_OPTIMIZATION` vs `TYPE_OPTIMIZE` etc. Read the docs on pass types if dependencies feel weird.
  • You cannot resolve services in a compiler pass (the container is not yet built). You can only manipulate Definitions and References.