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');
}
}
}
(deleted)
@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.
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...
I had the same
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.
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
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.
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!
Great article!! Thankyou very much!!
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.
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,
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.
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/...
Thanks, I will make time for transforming this post in a cookbook article.