Non-existent parameter from private bundle while loading extension twig

Symfony Parameter Availability: Twig Extension Load Order Error Postmortem

Summary

A Symfony 7.4 application failed to render Twig templates due to an unresolved parameter (so_core.routing) referenced in Twig’s global configuration. The root cause was premature parameter usage by the Twig extension before the bundle responsible for defining the parameter was initialized. The solution involved moving parameter registration to an early compilation phase using a compiler pass.

Root Cause

The error occurred due to Symfony’s dependency injection compilation order:

  • Loading sequence mismatch: Twig extensions load before bundle configuration methods (SoCoreBundle::build() or下定execute())
  • Parameter timing gap: so_core.routing was registered in SoCoreBundle::build()/loadExtension(), which executed after Twig initialized prodotto
  • Dependency graph violation: Twig unknowingly depended on a parameter from a bundle that hadn’t been processed yet

Key failure chain:

  1. Container compiles Twig extension first
  2. Twig reads globals.routing = '%so_core.routing%'
  3. Parameter so_core.routing is requested but nonexistent
  4. SoCoreBundle later registers the parameter (too late)

Why This Happens in Real Systems

Engineers encounter this due to Symfony’s deliberate optimization choices:

  • Unenforced dependency ordering: Bundles don’t declare cross-module dependencies allowing loading races
  • Static configuration limitations: Parameters in YAML/XML are parsed linearly, not respecting runtime dependencies
  • Lazy initialization trade-off: Symfony optimizes for speed by loading extensions early, sacrificing implicit ordering
  • Complex dependency graphs: Large projects with 50+ bundles make implicit ordering unpredictable

Real-World Impact

  • Immediate: All Twig-dependent functionality breaks (HTTP 500 errors on every page)
  • Operational: Admin dashboards/CRUD interfaces become unusable
  • Debugging costs: Average 5-8 hours spent tracing parameter propagation
  • Deployment blockers: Cannot release features relying on Twig-global parameters

Example or Code

Container compiler pass implementation:

// src/DependencyInjection/Compiler/ParameterCompilerPass.php
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class ParameterCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container): void
    {
        $config = $container->getExtensionConfig('so_core');
        $processedConfig = $container->getParameterBag()->resolveValue($config);
        $routingConfig = $processedConfig[0]['routing'] ?? [];

        $container->setParameter('so_core.routing', $routingConfig);
    }
}

Bundle registration:

// src/SoCoreBundle/SoCoreBundle.php
use App\DependencyInjection\Compiler\ParameterCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class SoCoreBundle extends Bundle
{
    public function build(ContainerBuilder $container): void
    {
        $container->addCompilerPass(new ParameterCompilerPass());
    }
}

How Senior Engineers Fix It

  1. Compiler pass prioritization:

    • Create a compiler pass that sets parameters during container compilation
    • Ensure passes run in PassConfig::TYPE_BEFORE_OPTIMIZATION phase
  2. Explicit parameter initialization:

    • Decouple parameter definition from bundle initialization
    • Compute parameters via Extension::processConfiguration() in the compiler
  3. Dependency hardening:

    • Add bundle dependencies via Bundle::getContainerExtensionClass()
    • Validate parameter existence with ParameterBag::resolveValue()
  4. Defensive checks:

    if (!$container->hasParameter('so_core.routing')) {
        throw new \RuntimeException('Missing so_core.routing parameter');
    }
  5. Architectural audit:

    • Generate dependency graphs using bin/console debug:container --parameters
    • Verify load order with bin/console debug:container --show-private

Why Juniors Miss It

  • Assumed linear processing: Expect bundles to initialize in registration order (false in Symfony)
  • Over-reliance on autowiring: Believe parameters “magically” appear everywhere
  • Testing gaps: Missing integration tests covering Twig initialization
  • Documentation blind spot: Symfony docs emphasize service wiring over parameter timing
  • Tooling underuse: Not leveraging debug:container to inspect parameter availability phases