Symfony Config component makes it possible to define semantic configuration, exposed to the end developer.
This configuration is validated by rules you define, e.g. validating type (string, array, integer, boolean, etc.).
Usually, once validated and processed, this semantic configuration is then mapped to internal key/value parameters stored in the ServiceContainer.

eZ Platform uses this for its core configuration, but adds another configuration level, the SiteAccess.
For each defined SiteAccess, you need to be able to use the same configuration tree in order to define SiteAccess-specific config.
These settings then need to be mapped to SiteAccess-aware internal parameters that you can retrieve via the ConfigResolver.
For this, internal keys need to follow the format <namespace>.<scope>.<parameter_name>.
namespaceis specific to your app or bundle, scopeis the SiteAccess, SiteAccess group, default or global,
and parameter_nameis the actual setting identifier.

The class's fully qualified name is eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\Configuration.
All you have to do is to extend it and use $this->generateScopeBaseNode():

When you define a hash as semantic config, you sometimes don't want the SiteAccess settings to replace the default or group values,
but enrich them by appending new entries. This is made possible by using $processor->mapConfigArray(),
which needs to be called outside the closure (before or after), in order to be called only once.

In the example above, entries were merged in respect to the scope order of precedence. However, if you define the planets key forsiteaccess1, it will completely override the default value since the merge process is done at only 1 level.

You can add another level by passing ContextualizerInterface::MERGE_FROM_SECOND_LEVEL as an option (third argument) to$contextualizer->mapConfigArray().

acme_example:system:siteaccess_group:setting_a:string:foobaros_types:[macos,linux]number:123# Assuming "siteaccess1" is part of "siteaccess_group"siteaccess1:setting_a:number:456enabled:truelanguage:[javascript,python]

Result of using ContextualizerInterface::MERGE_FROM_SECOND_LEVEL option:

As specified above, $contextualizer->mapConfigArray() is not to be used within the scope loop, like for simple values.
When using a closure/callable, you usually call it before or after $processor->mapConfig().
For mapper objects, a dedicated interface can be used: HookableConfigurationMapperInterface,
which defines 2 methods: preMap() and postMap().