Reputation: 1112
I have the following Configuration in my Symfony2 project:
<?php
namespace Acme\CommonBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
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_common');
$rootNode
->children()
->arrayNode('controller')
->children()
->arrayNode('controllers') // overwrite defaults declared above for specific controllers
->prototype('array')
->children()
->scalarNode('title')->end()
->append($this->addActionsNode())
->append($this->addViewsNode())
->end()
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}
/**
* Add the actions node, e.g.:
* actions:
* edit:
* name: Edit
* path: edit
* icon: icon-edit
* width: 45
* archive:
* name: Archive
* path: archive
* icon: icon-thrash
* width: 75
*
* These are the actions that will be shown in the index view
*
* @return ArrayNodeDefinition|NodeDefinition
*/
protected function addActionsNode()
{
$node = $this->getNode('actions');
$node
->useAttributeAsKey('key')
->prototype('array')
->children()
->scalarNode('name')->end()
->scalarNode('path')->end()
->scalarNode('icon')->end()
->integerNode('width')->end()
->end()
->end();
return $node;
}
/**
* Configure the templates used, e.g.:
* views:
* index: :AcmeCommon:Crud/list.html.twig
* edit: :AcmeCommon:Crud/edit.html.twig
*
* @return ArrayNodeDefinition|NodeDefinition
*/
protected function addViewsNode()
{
$node = $this->getNode('views');
$node
->children()
->scalarNode('index')->end()
->scalarNode('edit')->end()
->end();
return $node;
}
/**
* Helper function to build a node
*
* @param string $name
*
* @return \Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition|NodeDefinition
*/
protected function getNode($name)
{
$builder = new TreeBuilder();
$node = $builder->root($name);
return $node;
}
}
The idea is that I can configure some stuff for a bunch of CRUD controllers I'm writing. The actions and views node are optional. They should only be used if you want to overwrite certain defaults. Configuration could look like:
acme_common:
controller:
controllers:
acme_foo_controller:
title: Foo
actions:
edit:
name: Edit
path: edit
icon: icon-edit
width: 45
views:
index: :AcmeCommon:Foo/list.html.twig
edit: :AcmeCommon:Foo/edit.html.twig
acme_bar_controller:
title: Bar
views:
index: :AcmeCommon:Bar/list.html.twig
acme_baz_controller:
title: Baz
This results in an array that looks like:
Array
(
[controller] => Array
(
[controllers] => Array
(
[acme_foo_controller] => Array
(
[title] => Foo
[actions] => Array
(
[edit] => Array
(
[name] => Edit
[path] => edit
[icon] => icon-edit
[width] => 45
)
)
[views] => Array
(
[index] => :AcmeCommon:Foo/list.html.twig
[edit] => :AcmeCommon:Foo/edit.html.twig
)
)
[acme_bar_controller] => Array
(
[title] => Bar
[views] => Array
(
[index] => :AcmeCommon:Bar/list.html.twig
)
[actions] => Array
(
)
)
[acme_baz_controller] => Array
(
[title] => Baz
[actions] => Array
(
)
)
)
)
)
As you can see that when actions
isn't declared, it is still added as an empty array. I'd like it so that actions
is simply omitted and have the resulting array look like:
Array
(
[controller] => Array
(
[controllers] => Array
(
[acme_foo_controller] => Array
(
[title] => Foo
[actions] => Array
(
[edit] => Array
(
[name] => Edit
[path] => edit
[icon] => icon-edit
[width] => 45
)
)
[views] => Array
(
[index] => :AcmeCommon:Foo/list.html.twig
[edit] => :AcmeCommon:Foo/edit.html.twig
)
)
[acme_bar_controller] => Array
(
[title] => Bar
[views] => Array
(
[index] => :AcmeCommon:Bar/list.html.twig
)
)
[acme_baz_controller] => Array
(
[title] => Baz
)
)
)
)
Is this possible?
Upvotes: 3
Views: 1401
Reputation: 3378
I figured it out by stepping through the code with xdebug. It appears that the PrototypeNode
always has a default value, which is an empty array. Further probing revealed this empty array is applied during a finalization phase. But, the last step of this phase is to apply finalization closures, which happen to also be normalization closures. To remove the unwanted empty arrays, you need to add a normalize()->always()
to the parent node, and give it a closure which returns the modified array.
Your modified example:
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('acme_common');
$rootNode
->children()
->arrayNode('controller')
->children()
->arrayNode('controllers') // overwrite defaults declared above for specific controllers
->prototype('array')
->validate()
->always(function($v){
if ( empty($v['action']) )
unset($v['action']);
return $v;
})
->end()
->children()
->scalarNode('title')->end()
->append($this->addActionsNode())
->append($this->addViewsNode())
->end()
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}
Upvotes: 3
Reputation: 1
You can work on the configuration with the prepend intereface: http://symfony.com/doc/current/cookbook/bundles/prepend_extension.html There you can remove the empty array and get it again in the container.
Upvotes: 0