April 26, 2014
by Jason Roman





Categories:
Protips

Tags:
PHP


Automatically Set Bundle Configuration Container Parameters in Symfony

Instead of having to manually set every configurable container parameter for your bundle, let a few lines of code do it for you automatically.

In my previous post, I showed you how to use YAML to set your bundle configuration defaults. Now I'm going to show you how you can set these as container parameters automatically.

Symfony has documentation about how to set your parameters. Here is their example:

$container->setParameter('acme_hello.my_service_type', $config['my_type']);
This can become tedious quickly, especially if you start refactoring and changing names around, or if you have a lot of parameters to configure. Furthermore, their example is confusing since it gives a different name for the parameter compared to the config.

Let's go back to our hash generator. Here's the configuration for reference:
# /path/to/mybundle/Resources/config/config.yml
hash_generator:
length: 7
alphabet: bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ1234567890
and the corresponding service:

<service id="short_url.hash_generator" class="%short_url.hash_generator.class%">
<argument key="length">%short_url.hash_generator.length%</argument>
<argument key="alphabet">%short_url.hash_generator.alphabet%</argument>
</service>
According to the Symfony docs, we could configure our arguments manually like so:
// path/to/mybundle/DependencyInjection/ShortUrlExtension.php
public function load(array $configs, ContainerBuilder $container)
{
// ... prepare your $config variable
$container->setParameter(
'short_url.hash_generator.length', $config['hash_generator']['length']
);
$container->setParameter(
$this->getAlias().'.hash_generator.alphabet', $config['hash_generator']['alphabet']
);
}
No big deal, only a couple lines of code. Notice that you can also use the getAlias() function of the Extension class if you want the variable prefix to match the bundle's root name. But some of this seems a little redundant and requires you to remember to set the new parameters every time they are added. Let's set the parameters automatically instead:
// path/to/mybundle/DependencyInjection/ShortUrlExtension.php
public function load(array $configs, ContainerBuilder $container)
{
// ... prepare your $config variable
// set each bundle configuration value as a container parameter
foreach ($config as $key => $value) {
$container->setParameter('short_url.'.$key, $value);
// or $container->setParameter($this->getAlias().'.'.$key, $value);
}
}
If you are a keen observer you'll realize that this isn't exactly what we want (although if it's all you need, you can skip the rest). This only takes the top level, so we end up with one parameter named short_url.hash_generator that is an array with keys length and alphabet, and nothing else. I want to set length and alphabet as their own parameters. So, let's modify and use the array_walk() function call to drill down further:
// path/to/mybundle/DependencyInjection/ShortUrlExtension.php
public function load(array $configs, ContainerBuilder $container)
{
// ... prepare your $config variable
// set each bundle configuration value as a container parameter
array_walk(
$config,
array($this, 'setParameters'),
array('parentKey' => 'short_url', 'container' => $container)
// or array('parentKey' => $this->getAlias(), 'container' => $container)
);
}

public function setParameters($value, $key, $params)
{
// set the parameter name by appending the current key to the parent
$parameterName = $params['parentKey'].'.'.$key;

// set the container parameter
$params['container']->setParameter($parameterName, $value);

// if the value is an array, recursively call this function for each array value
if (is_array($value))
{
array_walk($value, array($this, 'setParameters'),
array(
'parentKey' => $parameterName,
'container' => $params['container']
)
);
}
}
Okay, that's a lot to process. The array_walk() call replaces the foreach and loops through every config value. It passes the service container which is needed for setting the parameter, and also passes 'short_url' as the parent key that will be the prefix for all of our parameters. In the setParameters() function itself, we start by setting the container parameter for the current key and value. So our first time through this function will set short_url.hash_generator as its own parameter.

In addition, the function also detects that this is an array and recursively calls the setParameters() function to drill down through the rest of the array. Here is where the parameters short_url.hash_generator.length and short_url.hash_generator.alphabet will be set. Note that I was unable to use array_walk_recursive() to accomplish this because I needed to keep passing along the parent key. So now we have our three parameters automatically set:
  • short_url.hash_generator
  • short_url.hash_generator.length
  • short_url.hash_generator.alphabet
That's it! I know this isn't a feature you'll always want to use, and if you only have one or two parameters it might be overkill, but this gives an alternative solution that is flexible and will automatically handle any configuration updates to your bundle without having to set new parameters manually.