Symfony2: define your bundle's configuration values using the TreeBuilder
Matthias Noback
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
. This
TreeBuilderallows 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');
}
}
}