Container Extensions
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.