ezsystems/ezpublish-kernel — pull request #931 by lolautruche (2014-07-16 11:57:54+00:00)
Title: Fix EZP-23094: Make SiteAccess aware semantic configuration reusable and extensible

https://jira.ez.no/browse/EZP-23094

This patch makes it easy to implement a *SiteAccess aware* semantic configuration and its mapping to internal config for any eZ bundle developer.

## Semantic configuration parsing
An abstract `Configuration` class has been added, simplifying the way to add a SiteAccess settings tree like the following:

```yaml
acme_demo:
system:
my_siteaccess:
hello: "world"
foo_setting:
an_integer: 456
enabled: true

my_siteaccess_group:
hello: "universe"
foo_setting:
foo: "bar"
some: "thing"
an_integer: 123
enabled: false
```

All you have to do is to extend it and use `$this->generateScopeBaseNode()`:

```php
namespace Acme\DemoBundle\DependencyInjection;

use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\Configuration as ScopeConfiguration;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;

class Configuration extends ScopeConfiguration
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root( 'acme_demo' );

// $systemNode will then be the root of siteaccess aware settings.
$systemNode = $this->generateScopeBaseNode( $rootNode );
$systemNode
->scalarNode( 'hello' )->isRequired()->end()
->arrayNode( 'foo_setting' )
->children()
->scalarNode( "foo" )->end
->scalarNode( "some" )->end
->integerNode( "an_integer" )->end
->booleanNode( "enabled" )->end()
->end()
->end();

return $treeBuilder;
}
```

## Mapping to internal settings
Semantic configuration must always be *mapped* to internal *key/value* settings within the ServiceContainer. Usually this is done in the DIC extension.
For SiteAccess aware settings, new `ConfigurationProcessor` and `Contextualizer` classes have been introduced to ease the process.

```php
namespace Acme\DemoBundle\DependencyInjection;

use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ConfigurationProcessor;
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface;
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 AcmeDemoExtension extends Extension
{
public function load( array $configs, ContainerBuilder $container )
{
$configuration = $this->getConfiguration( $configs, $container );
$config = $this->processConfiguration( $configuration, $configs );

$loader = new Loader\YamlFileLoader( $container, new FileLocator( __DIR__.'/../Resources/config' ) );

$processor = new ConfigurationProcessor( $container, 'acme_demo' );
$processor->mapConfig(
$config,
function ( $scopeSettings, $currentScope, ContextualizerInterface $contextualizer )
{
$contextualizer->setContextualParameter( 'hello', $currentScope, $scopeSettings['hello'] );
}
);

// Now map "foo_setting" and ensure keys defined for "my_siteaccess" overrides the one for "my_siteaccess_group"
// It is done outside the closure as it is needed only once.
$contextualizer = $processor->getContextualizer();
$contextualizer->mapConfigArray( 'foo_setting', $config );
}
}
```

### Dedicated mapper object
Instead of passing a callable to `$processor->mapConfig()`, an instance of `eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ConfigurationMapper` can be passed, for example if you have a lot of configuration to map and don't want to *pollute* your DIC extension class.