Skip to content

Container Extensions

Edit this page

An extension is a class that loads and manages the service configuration of a group of related services. Extensions are how bundles integrate with the service container: when you configure a key like framework or twig in a config file, the extension registered with that alias processes those values and turns them into service definitions and parameters.

You only need to create your own extensions in two cases:

  • When creating a bundle: read that article instead, because it explains the utilities that Symfony provides to define bundle extensions;
  • When using the container in standalone PHP applications: extensions allow each module of the application to register and manage its own service configuration. This is what the rest of this article explains.

Creating an Extension

Extensions must implement ExtensionInterface. The main work of the extension is done in its load() method, where you load configuration files and manipulate the service definitions. A simple extension may just load the service configuration files of its module:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

class AcmeDemoExtension implements ExtensionInterface
{
    public function load(array $configs, ContainerBuilder $container): void
    {
        $loader = new PhpFileLoader(
            $container,
            new FileLocator(__DIR__.'/../Resources/config')
        );
        $loader->load('services.php');
    }

    // ...
}

The load() method receives a fresh container, which is merged into the main container after the extension has set it up. This allows each extension to manage its service definitions independently.

The interface defines another method called getAlias(), which returns the configuration key associated with the extension:

1
2
3
4
5
6
7
8
9
10
11
// ...

class AcmeDemoExtension implements ExtensionInterface
{
    // ...

    public function getAlias(): string
    {
        return 'acme_demo';
    }
}

Note

Symfony provides a base Extension class which implements the methods of the interface, as well as a shortcut method for processing the configuration. See How to Load Service Configuration inside a Bundle for more details.

Registering and Loading Extensions

Register the extension in the container with the registerExtension() method. From then on, any section of the loaded configuration files that uses the extension alias as its key is passed to the load() method of the extension:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$container->registerExtension(new AcmeDemoExtension());

$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('config.yaml');

// ...
$container->compile();

If the config.yaml file of the previous example contains an acme_demo section, its values are passed to the extension:

1
2
3
4
# config.yaml
acme_demo:
    foo: fooValue
    bar: barValue

Unlike the configuration loaded directly into the container, the extension configuration is not processed immediately: extensions are loaded when calling the compile() method of the container.

Note

When loading a config file that uses an extension alias as a key, the extension must already have been registered with the container builder or an exception will be thrown.

Note

Registering an extension is not enough to include it in the compiled container: the extension must receive some configuration, like the acme_demo section of the previous example. If there's no configuration to pass, tell the container builder to load the extension anyway with the loadFromExtension() method:

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$extension = new AcmeDemoExtension();
$container->registerExtension($extension);
$container->loadFromExtension($extension->getAlias());
$container->compile();

Note

If you need to manipulate the configuration loaded by an extension, you cannot do it from another extension, because each extension uses a fresh container. Use a compiler pass instead, which works with the full container after all the extensions have been processed. The extension itself can also act as a compiler pass.

Processing the Configuration Values

The first argument of the load() method is an array with the values of every configuration section that uses the alias of the extension. In the previous example, it looks like this:

1
2
3
4
5
6
$configs = [
    [
        'foo' => 'fooValue',
        'bar' => 'barValue',
    ],
];

There's one array item per config file that defines values for the extension (even when a single file was loaded, like in this example). You could merge the different arrays yourself, but it's much better to use the Config component to merge and validate them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Symfony\Component\Config\Definition\Processor;
// ...

public function load(array $configs, ContainerBuilder $container): void
{
    $configuration = new Configuration();
    $processor = new Processor();
    $config = $processor->processConfiguration($configuration, $configs);

    $foo = $config['foo']; // fooValue
    $bar = $config['bar']; // barValue

    // ...
}

Then, use the processed values in your extension; for example, to define container parameters or to load additional configuration files conditionally:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function load(array $configs, ContainerBuilder $container): void
{
    $configuration = new Configuration();
    $processor = new Processor();
    $config = $processor->processConfiguration($configuration, $configs);

    $container->setParameter('acme_demo.foo', $config['foo']);

    $loader = new PhpFileLoader(
        $container,
        new FileLocator(__DIR__.'/../Resources/config')
    );
    $loader->load('services.php');

    if ($config['advanced']) {
        $loader->load('advanced.php');
    }
}

Deprecating Extension Parameters

You can deprecate the container parameters defined by your extension to warn users about not using them anymore. This helps with the migration across major versions of the extension. Deprecation is only possible when using PHP to configure the extension, not when using YAML. Use the ContainerBuilder::deprecateParameter() method to provide the deprecation details:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function load(array $configs, ContainerBuilder $container): void
{
    // ...

    $container->setParameter('acme_demo.database_user', $configs['db_user']);

    $container->deprecateParameter(
        'acme_demo.database_user',
        'acme/database-package',
        '1.3',
        // optionally you can set a custom deprecation message
        '"acme_demo.database_user" is deprecated, you should configure database credentials with the "acme_demo.database_dsn" parameter instead.'
    );
}

The parameter being deprecated must be set before being declared as deprecated. Otherwise a ParameterNotFoundException exception will be thrown.

Prepending Configuration

An extension can prepend the configuration of any other extension before the load() method is called. To do so, implement PrependExtensionInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
// ...

class AcmeDemoExtension implements ExtensionInterface, PrependExtensionInterface
{
    // ...

    public function prepend(ContainerBuilder $container): void
    {
        // ...

        $container->prependExtensionConfig($name, $config);

        // ...
    }
}

For more details, see How to Simplify Configuration of Multiple Bundles, which is specific to the Symfony Framework, but contains more details about this feature.

Using the Extension as a Compiler Pass

The load() method of an extension can't modify the services of other extensions, because each extension works on its own fresh container. To do that you need a compiler pass, which runs after all the extensions are loaded and works with the full container.

Instead of creating a separate compiler pass class, the extension itself can act as one: implement CompilerPassInterface and its process() method will be called during the compilation, without needing to register the extension as a compiler pass:

1
2
3
4
5
6
7
8
9
10
11
12
// ...
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;

class AcmeDemoExtension implements ExtensionInterface, CompilerPassInterface
{
    public function process(ContainerBuilder $container): void
    {
        // ... do something during the compilation
    }

    // ...
}

Note

The process() method of the extension class is called during the PassConfig::TYPE_BEFORE_OPTIMIZATION step. Create a separate compiler pass if you need to edit the container during another step.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version