Validating dynamic configuration keys in Symfony2

Enrise

9 april 2015

Yesterday I built a new feature for the TacticianBundle. If you haven’t checked out Tactician yet, I can recommend you to take a look at it. 
Anyway, I found something out that wasn’t very well documented on the net, so I’m sharing it here.

Validating dynamic configuration keys in Symfony2
So consider the following configuration, which defines two items under ‘commandbus’: default and queued.
There is also property that defines the name of the bundle’s default command bus.

tactician:
  default_bus: queued
  commandbus:
    default:
      middleware:
        - tactician.middleware.command_handler
    queued:
      middleware:
        - tactician.middleware.queued_command_handler

Obviously the configuration is invalid if the default_bus is not a valid command bus name, so we have to test against this constraint when loading the configuation. It took me quite some time to figure out how to do this properly, but it turned out to be pretty easy.

ifNotInArray() – Didn’t work
So the first thing you’ll find in the Symfony2 documentation is the `ifNotInArray()` constraint. This works when you have a predefined array of valid values, but not when the array values come from the user’s configuration.

Custom validate() logic – Works great!
What you have to do is write some code that loops over the configuration and saves all the keys under ‘commandbus’. These are the valid values for ‘default_bus’.

You can do this in the Configuration class of your bundle. Like so:

// ..Bundle/DependencyInjectionConfiguration::getConfigTreeBuilder
$rootNode
    ->validate()
        ->ifTrue(function($config) {
            return is_array($config) &&
            array_key_exists('default_bus', $config) &&
            array_key_exists('commandbus', $config);
        })
        ->then(function($config) {
            $busNames = array_keys($config[‘commandbus’]);
            if (!in_array($config['default_bus'], $busNames)) {
                // Throw an exception
            }
        })
    ->end();

The main advantage of this solution is that this validation logic is (still) only executed when the configuration cache is warming up, so not for every request.

I hope this saves you some time!