Provide your Symfony bundle with a configuration using extensions

Part of the work when programming applications is to make the code generic and reusable when the functionality you are working on is common to multiple use cases. A good way of doing this is by providing some configuration options that the users of your code can fill in order to customize their experience of your feature. Even for you, if you are the only user, you’ll appreciate having just to change a configuration key to update the behavior of your application, instead of diving into the code that you wrote (long) before.

Fortunately, Symfony provides a nice way to set up configuration options in your bundles, with very fine tuning. You probably already had a taste of it by looking to the app/config.yml file of the Symfony standard distribution, which ships with a basic configuration suitable for most applications. In order to support this feature, you must start by creating an extension class for your bundle.

Creating an extension

An extension is just a class that lies in the DependencyInjection folder of your bundle, under the name AcmeExtension if your bundle is named AcmeBundle (just replace the “Bundle” part with “Extension”), and Symfony will automatically include it. Note that you can optionally change the name of your extension’s class by overriding the getContainerExtensionClass() method of the base Bundle class.

The easiest way to create a new extension is to extend the abstract Extension class provided by Symfony, so you just need to implement the load() method. The method is automatically called by Symfony when building the container, and provided with the full configuration array from the different configuration files of your application, along with the container builder. You can then define some new container parameters or modify some service definitions according to the values of the configuration.

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = $this->getConfiguration($configs, $container);
    $config = $this->processConfiguration($configuration, $configs);
    // you can now read use the $config array which has been processed validated
}

As you can see, an extension also provides a getConfiguration() method to instantiate a Configuration object, which holds the definition of the configuration keys available to the user. It will automatically look for a Configuration class inside the DependencyInjection of you bundle, but you can manually instantiate it if you prefer to do so (or if it is located in an uncommon path).

Customizing a configuration

To define a Configuration class, you must implement the ConfigurationInterface and the getConfigTreeBuilder() that comes along. This tree builder is the way Symfony lets you define the configuration tree for you bundle, so you can easily choose the name of the keys to use and their format. It’s a really powerful component to use and it possible to define almost every configuration tree you may think of.

Here’s an example of a configuration that let you defined 3 parameters ‘foo’, ‘bar’ and ‘qux’ :

public function getConfigTreeBuilder()
{
    $treeBuilder = new TreeBuilder();
    $rootNode = $treeBuilder->root('acme');
    $rootNode
        ->children()
            ->scalarNode('foo')->end()
            ->booleanNode('bar')->defaultValue(true)->end()
            ->arrayNode('qux')->end()
        ->end()
    return $treeBuilder;
}

The first thing to do is defining a root node for the tree, which corresponds to the top key of your bundle’s configuration. It is named according to your extension’s name in lower case. After that, you may add as much node as you want to the root node.

Here, the ‘foo’ parameter is a scalar node, meaning it can take any scalar value (boolean, string, integer, float or null). ‘bar’ enforces a boolean value, with a default to true if the key is not defined by the user. You can also ave some configuration key expect an array, as it is the case for ‘qux’.

This is just a quick overview of the type of nodes available in the trees, but you can really do some tricky stuff with this, like embedding some nodes in some parent nodes, defining custom validation rules for the values, etc.

The user of your bundle just needs to fill the values of the configuration to customize the behavior of the bundle.

acme:
    foo: hello world
    bar: false
    qux: [john, doe]

Processing the configuration data

Now, back in your extension, you can just use the configuration in any way you want, like modifying a service configuration:

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = $this->getConfiguration($configs, $container);
    $config = $this->processConfiguration($configuration, $configs);
    
    // change the first argument of a service
    $container->findDefinition('my_custom_service')->replaceArgument(0, $config['foo']);
    // create a container parameter
    $container->setParameter('acme.bar.value', $config['bar']);
}

This is how the configuration of some services can be modified with the configuration. Many bundles use this feature extensively to provide their user with the maximum flexibility, and if you want to distribute a bundle to the community, you should really do the same in order to support the various needs of each user.