In Symfony 2 there are two main ways to define configuration options for a bundle. The first way is to simply define parameters in the service container and the second is to expose semantic configuration for your bundle. Whether to use parameters or expose semantic configuration should be determined by the type of configuration needed and whether the bundle will be reused and/or distributed. Semantic configuration offers many benefits over simply using parameters, but is slightly more involved to implement.

 

Using Parameters

Using parameters in your application allows you to keep simple configuration, account details, api keys, email addresses, and magic numbers in a single place. The benefits of using parameters are that they decouple your configuration and constants from your code. They also allow you to use separate sets of parameters for different environments. Symfony has excellent documentation on how to use parameters. There are some drawbacks to using parameters though. Users of the bundle need to know they exist and how to use them. There is no enforcement or validation, and parameters can’t be logically nested. Parameters are limited to configuration constants, and if an entire portion of the bundle may change based on configuration then a parameter may not be a good choice.

 

Exposing Semantic Configuration

A great alternative to relying solely on parameters is to expose semantic configuration for your bundle. This allows users of the bundle to specify configuration options in the main configuration file. Some of the benefits of using configuration vs. parameters are below.

– Using configuration can allow you to completely change the way services are defined and what services are available by loading service files differently based on configuration values.

– Configuration options can also use default values, enforce required settings, be validated, and restrict values to a list of available options.

– Configuration can also be nested to allow for a tree of settings and to logically group settings.

– Configuration can also be self documenting, allowing users of the bundle to easily determine default settings and available options.

– Configuration also decouples the parameters from the services that use them, which are often included in the same file within a bundle. It allows configuration options to remain with the app and parameters to be portable with the bundle.

There is a higher level of effort required to use configuration as opposed to parameters and the process is a slightly more complex.

 

Defining Configuration

Configuration Class

The configuration class, located in a bundles DependencyInjection directory, allows a bundle to expose semantic configuration. This allows users to set bundle options by adding to Symfony’s main configuration files, ie. app/config/config.yml. By adding configuration using the TreeBuilder, you can expose different configuration options, default values, allowed options, and validation rules to create a configuration tree. Symfony has excellent documentation on how to define and process configuration values so I won’t go into complete detail beyond a simple example of defining a bundle configuration.

The tree builder starts with a root node, which is created by default when using the app_console generate:bundle command. Setting the children of the root node will add configuration options nested beneath it in the configuration tree. Possible child node types are scalar, boolean, integer, float, enum, array, and variable. It is possible to set deafult or required values using the defaultValue() method. More advanced validation can also be added, such as testing if a value is a string or in an array.

This example shows defining a few different configuration settings, including child configuration, basic validation, and default values.

<?php
namespace Acme\AppBundle\DependencyInjection;

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

/**
 * This is the class that validates and merges configuration from your app/config files
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('acme_app');

        $rootNode
            ->children()
                ->scalarNode('my_scaler_node')
                    ->defaultValue('A sensible default value')
                ->end()
                ->booleanNode('my_boolean_node')
                    ->defaultTrue()
                ->end()
                ->enumNode('my_enum_node')
                    ->values(array('option_a', 'option_b', 'option_c'))
                    ->isRequired()
                ->end()
                ->arrayNode('my_array_node')
                    ->children()
                        ->scalarNode('child_setting_one')->end()
                        ->scalarNode('child_setting_two')->end()
                        ->scalarNode('child_setting_three')->end()
                    ->end()
                ->end()
            ->end()
        ;

        return $treeBuilder;
    }
}

 

Setting Configuration Options

This is how you would configure these options in app/config/config.yml file.

# app/config/config.yml

acme_app:
    my_scaler_node: "A configured value"
    my_boolean_node: false
    my_enum_node: "option_b"
    my_array_node:
        child_setting_one: "child configuration setting one"
        child_setting_two: "child configuration setting two"
        child_setting_three: "child configuration setting three"

 

Documenting the Configuration

After creating this configuration, full configuration definition documentation can be viewed using the app/console config:dump-reference terminal command. This will show the default bundle configuration in yml, including whether nodes are required to be set. The configuration above looks like this.

$ app/console config:dump-reference acme_app
Default configuration for extension with alias: "acme_app"
acme_app:
    my_scaler_node:       A sensible default value
    my_boolean_node:      true
    my_enum_node:         ~ # Required
    my_array_node:
        child_setting_one:    ~
        child_setting_two:    ~
        child_setting_three:  ~

 

Dependency Injection Extension Class

The dependency injection extension class allows your bundle to load service configuration, and this is where you can access the configuration options that are set in the main config file, ie. app/config/config.yml. This is where you can take action based on the configuration options. For instance, you can convert the configuration into different parameters that can then be injected into services or you can conditionally load additional configuration files based on the configuration values.

Below, the load() method is merging the configuration, accessing that configuration and setting parameters that can later be used by other services.

<?php
namespace Acme\AppBundle\DependencyInjection;

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

/**
 * This is the class that loads and manages your bundle configuration
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
 */
class AcmeAppExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $this->updateContainerParameters($container, $config);

        // Load before the configuration parameters get set.
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');
    }

    /**
     * Update parameters using configuratoin values.
     *
     * @param ContainerBuilder $container
     * @param array $config
     */
    protected function updateContainerParameters(ContainerBuilder $container, array $config)
    {
        // Set a parameter for each configuration value
        $container->setParameter('acme_app.my_scaler_node', $config['my_scaler_node']);
        $container->setParameter('acme_app.my_boolean_node', $config['my_boolean_node']);
        $container->setParameter('acme_app.my_enum_node', $config['my_enum_node']);
        $container->setParameter('acme_app.my_array_node.child_setting_one', $config['my_array_node']['child_setting_one']);
        $container->setParameter('acme_app.my_array_node.child_setting_two', $config['my_array_node']['child_setting_two']);
        $container->setParameter('acme_app.my_array_node.child_setting_three', $config['my_array_node']['child_setting_three']);
    }
}

 

Another option is to conditionally load an additional configuration file that contains more service and parameter definitions. This file could override some default services or redefine a number of parameters, depending on what you need to do. This example shows how to load an optional services file based on the value of a configuration option.

<?php
namespace Acme\AppBundle\DependencyInjection;

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

/**
 * This is the class that loads and manages your bundle configuration
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
 */
class AcmeAppExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        // Load before the configuration parameters get set.
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');
        
        if ($config['my_boolean_node']) {

            $loader->load('optional_services.yml');
        }
    }
}

 

Supporting XML

There are some additonal steps that need to be taken to support the use of XML configuration files. You’ll need to define a namespace and create an XML schema. More information on that can be found here.

 

Conclusion

If your bundle does not rely heavily on parameters and will not be reused then a simple parameter may be all you need. Exposing semantic configuration in Symfony is a little more involved than simply using parameters, but if you intend to share or reuse your bundle on multiple projects then it is well worth the additional effort.