After writing this article I've started to write more documentation for the Config component. First I published it here, then it became the official documentation of the Config component.

After reading the Cookbook article How to expose a Semantic Configuration for a Bundle I became interested in the possibilities for defining a configuration tree using the TreeBuilder from the Symfony component Config. ThisTreeBuilderallows you to build a schema against which the configuration values (or short: config) from/app/config.yml` and the like are to be validated.

Create an Extension

First make sure that your bundle has a /DependencyInjection/[BundleNameWithoutBundle]Extension.php containing the extension class:

namespace Acme\DemoBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Processor;

class AcmeDemoExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $processor = new Processor();
        $configuration = new Configuration();
        $config = $processor->processConfiguration($configuration, $configs);

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

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

This enables you to use the key "acme_demo" in the /app/config.yml file, like this:

acme_demo:
    enabled: true

But right now, this would cause an exception to be thrown, which says that the "enabled" key is not defined as a config value.

The Configuration class

To make the "enabled" key valid, we have to make sure the file /DependencyInjection/Configuration.php exists in your bundle. This file contains the Configuration class for your bundle and it's method getConfigTreeBuilder, which should return the TreeBuilder object that describes the structure of your config values:

namespace Acme\DemoBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('acme_demo');

        return $rootNode;
    }
}

BooleanNode

There are many ways to specify validation rules for configuration values, but first, let's create a fresh node in the tree for our still invalid value "enabled". The value should always be of type "boolean", so before we return $rootNode, we add:

$rootNode->
    children()
        ->booleanNode('enabled')->end()
    end()
;

As you can see, the TreeBuilder and the nodes all implement a fluent interface, so we can write the tree down in a semantic way: the way it looks reflects the way it is.

The calls to end() mean that we want to move up again to the parent node of the current node.

ScalarNode

Let's add a node for scalar values (like strings, numbers, etc.):

$rootNode->
    children()
        ->booleanNode('enabled')->end()
        ->scalarNode('default_user')
            ->isRequired()
            ->cannotBeEmpty()
        ->end()
    end()
;

The new node defines a config value "default_user" which is required and may not be empty.

ArrayNode

Now, let's add an array node, which allows for an array of users to be defined in the config file:

$rootNode->
    children()
        ->booleanNode('enabled')->end()
        ->scalarNode('default_user')
            ->isRequired()
            ->cannotBeEmpty()
        ->end()
        ->arrayNode('users')
            ->requiresAtLeastOneElement()
            ->prototype('array')
                ->children()
                    ->scalarNode('full_name')
                        ->isRequired(true)
                    ->end()
                    ->booleanNode('is_active')
                        ->defaultValue(true)
                    ->end()
                ->end()
            ->end()
        ->end()
    ->end()
;

A few other things are shown here: the config value "user" contains multiple subnodes, of which the prototype is an array node. The children of these array nodes themselves will be a scalar node called "full_name" and a boolean node called "is_active", of which the default value is true. An extra requirement is that at least one such user should be defined.

Before normalization - then what?

The final thing I want to show is how to change the overall structure of the config values, before they are validated. This would allow you to add shortcuts, or special ways of handling certain config structures (see the DoctrineBundle Configuration class for a beautiful example of this: the only connection defined will be moved to the "connections" section, before processing). In my example, I remove all the user definitions, when the value of "enabled" is false or is not set:

$rootNode->
    ->beforeNormalization()
        ->ifTrue(function($v) {
            // $v contains the raw configuration values
            return !isset($v['enabled']) || false === $v['enabled'];
        })
        ->then(function($v) {
            unset($v['users']);
            return $v;
        })
        ->end()
    ->children()
        // ...
    ->end()
;

You can see here that we first check (using ifTrue()) if the value of "enabled" is false or is not set at all. If this is the case, we unset the "users" key in the array containing the raw config values.

A setting like "enabled" may also be very important for deciding whether you should load the services of your bundle or not. An example usage of this would be to make the loading of your services.xml file conditional in /DependencyInjection/AcmeDemoBundleExtension.php:

class AcmeDemoExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $processor = new Processor();
        $configuration = new Configuration();
        $config = $processor->processConfiguration($configuration, $configs);

        if (isset($config['enabled']) && $config['enabled']) {
            $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
            $loader->load('services.xml');
        }
    }
}
PHP Symfony2 dependency injection service container configuration
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
(deleted)
Matthias Noback

@BlueNinjaSmurf:disqus Didn't notice this comment. Could you please update your language please? This is not a place for cursing. If you don't do this, I'll just delete your comment. You could also submit an issue at the Symfony documentation GitHub repository. Please mind your attitude there.

TheProgrammer

I had the same problem as you. Here is how you use the configuration once defined.

Either pass the whole container to your service (bad) :
$this->container->getConfiguration('my_config.value');
Or better, pass the individual values to your services :
public function __construct($value)
{
$this->value = value;
}
https://groups.google.com/d...

TheProgrammer

I had the same

Robin Chalas

There is a little syntax mistake in the code block of the ScalarNode and BooleanNode sections , the '->' before the lasts 'end()' are missing.
Thank you, for this great article.

tiana randrianarivony

Hi, very explained, so thanks, I just want to ask how to import my config.xml from extension bundle as it explains https://doc.ez.no/display/E... , unfortunately it only for yaml file.
Thank you

Israel Carberry

The Configuration::getConfigTreeBuilder() should return $treeBuilder, not $rootNode.

Also, if the custom parameters are injected into any of the loaded services, then they need to be set in the $container first. Something like this:

$config = $this->processConfiguration($configuration, $configs);

foreach ($config as $name => $node) {
$container->setParameter('acme_demo.' . $name, $node);
}

Now %acme_demo.default_user% can be injected into a service defined in services.xml.

Lars Janssen

I can't find it explicitly stated anywhere on the web, but I wanted to explicitly state that a node is required, even though it has a default value:

->scalarNode('foo')->defaultValue('bar')->isRequired()->end()

However, this works the same as:

->scalarNode('foo')->isRequired()->end()

because I get a config error if I don't specify a config item for 'foo'.

Why did I want to specify this redundant ->isRequired()? Because if you look at the code like this:

->scalarNode('foo')->defaultValue('bar')->end()

how do you know if the node "foo" is essential for the operation of the bundle? Was the default value provided for convenience, or is it a required value?

Having said all that, it's such a small thing (matter of taste; maybe not worth the extra code), so I'm only mentioning it in case it catches anyone else!

Jesús Miguel Benito Calzada

Great article!! Thankyou very much!!

Amin Alizade

Thank you for great article. It was so useful.

great explanation,

please write about required parameters and its behaviour, as my child do not force to be set.

Matthias Noback

Hi Pawel,
Thanks, though it was not for you personally ;) I just posted this: http://php-and-symfony.matt... It also has a part about required parameters.
Good luck,

cordoval

i am going ahead https://github.com/symfony/... but please feel free to work on the sections you know best, I am not an expert so I just get my commits and PR over it and I will rebase and we move like that working on the same documentation together.

keymaster

Since this is the best explanation that has been available for several months, there really isn't anything else, perhaps you might consider submitting this blog post as a cookbook article.

I took the liberty of referencing this post here:
https://github.com/symfony/...

Matthias Noback

Thanks, I will make time for transforming this post in a cookbook article.