Compiler Passes
Hooks that modify the service container during the compile step. Used to wire up tagged services, swap implementations, validate config.
- 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.