gremo
gremo

Reputation: 48439

Allowing key-value pairs in Symfony 2 Bundle semantic configuration

Basically i'd like to allow an arbitrary (but not empty) number of key-value pairs in my configuration, under billings node, that is define an associative array.

I've this in my Configurator.php (part of):

->arrayNode('billings')
    ->isRequired()
    ->requiresAtLeastOneElement()
    ->prototype('scalar')
    ->end()
->end()

And then, in my config.yml:

my_bundle:
    billings:
        monthly : Monthly
        bimonthly : Bimonthly

However, outputting $config:

class MyBundleExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new Loader\YamlFileLoader($container,
            new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');

        $processor     = new Processor();
        $configuration = new Configuration();

        $config = $processor->processConfiguration($configuration, $configs);

        $container->setParameter('my_bundle.billings', $config['billings']);

        var_dump($config);
        die();
    }

}

... what i get is array index by numbers, not an associative one:

 'billings' => 
     array (size=2)
         0 => string 'Monthly' (length=7)
         1 => string 'Bimonthly' (length=9)

Out of curiosity (and if this can help), i'm trying to inject this array as a service parameter (annotations from this great bundle: JMSDiExtraBundle):

class BillingType extends \Symfony\Component\Form\AbstractType
{

    /**
     * @var array
     */
    private $billingChoices;

    /**
     * @InjectParams({"billingChoices" = @Inject("%billings%")})
     *
     * @param array $billingChoices
     */
    public function __construct(array $billingChoices)
    {
        $this->billingChoices = $billingChoices;
    }
} 

Upvotes: 21

Views: 9745

Answers (4)

numediaweb
numediaweb

Reputation: 17030

This is what useAttributeAsKey actually does:

/**
 * Sets the attribute which value is to be used as key.
 *
 * This is useful when you have an indexed array that should be an
 * associative array. You can select an item from within the array
 * to be the key of the particular item. For example, if "id" is the
 * "key", then:
 *
 *     array(
 *         array('id' => 'my_name', 'foo' => 'bar'),
 *     );
 *
 *   becomes
 *
 *     array(
 *         'my_name' => array('foo' => 'bar'),
 *     );
 *
 * If you'd like "'id' => 'my_name'" to still be present in the resulting
 * array, then you can set the second argument of this method to false.
 *
 * This method is applicable to prototype nodes only.
 *
 * @param string $name          The name of the key
 * @param bool   $removeKeyItem Whether or not the key item should be removed
 *
 * @return ArrayNodeDefinition
 */
public function useAttributeAsKey($name, $removeKeyItem = true)
{
    $this->key = $name;
    $this->removeKeyItem = $removeKeyItem;

    return $this;
}

Upvotes: 0

Stphane
Stphane

Reputation: 3466

I recently had to setup some nested arrays configuration.

Needs were

  • A first level array with custom keys (discribing an entity path)
  • Each of those arrays had to have one or more arbitrary tags as keys, with boolean value.

To achieve such a configuration, you have to casacade prototype() method.

So, the following configuration:

my_bundle:
    some_scalar: my_scalar_value
    first_level:
        "AppBundle:User":
            first_tag: false
        "AppBundle:Admin":
            second_tag: true
            third_tag: false

can be obtained using the following configuration:

$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('my_bundle');
$rootNode
    ->addDefaultsIfNotSet() # may or may not apply to your needs
    ->performNoDeepMerging() # may or may not apply to your needs
    ->children()
        ->scalarNode('some_scalar')
            ->info("Some useful tips ..")
            ->defaultValue('default')
            ->end()
        ->arrayNode('first_level')
            ->info('Useful tips here..')
            # first_level expects its children to be arrays
            # with arbitrary key names
            ->prototype('array')
                # Those arrays are expected to hold one or more boolean entr(y|ies)
                # with arbitrary key names
                ->prototype('boolean')
                    ->defaultFalse()->end()
                ->end()
            ->end()
        ->end()
    ->end();
return $treeBuilder;

Dumping $config from inside the extension class gives the following output:

Array
(
    [some_scalar] => my_scalar_value
    [first_level] => Array
        (
            [AppBundle:User] => Array
                (
                    [first_tag] => 
                )

            [AppBundle:Admin] => Array
                (
                    [second_tag] => 1
                    [third_tag] => 
                )
        )
)

And voilà !

Upvotes: 2

Leonov Mike
Leonov Mike

Reputation: 923

You should add useAttributeAsKey('name') to your billing node configuration in Configurator.php.

More information about useAttributeAsKey() you can read at Symfony API Documentation

After changes billing node configuration it should be like:

->arrayNode('billings')
    ->isRequired()
    ->requiresAtLeastOneElement()
    ->useAttributeAsKey('name')
    ->prototype('scalar')->end()
->end()

Upvotes: 25

lividgreen
lividgreen

Reputation: 31

You may add something like:

->arrayNode('billings')
    ->useAttributeAsKey('whatever')
    ->prototype('scalar')->end()

After that keys will magically appear.

See: https://github.com/symfony/symfony/issues/12304

Upvotes: 2

Related Questions