Skip to content

Commit

Permalink
[FrameworkBundle] Integrate Configuration\Builder class for config me…
Browse files Browse the repository at this point in the history
…rging and normalization

This fixes some BC problems introduced in f9138d3. Some top-level can now be simply enabled by providing true/null in PHP/YAML. Additionally, the Configuration\Builder allows options to be unset by providing "false" (helpful for overriding activation in a previous config file). All options supporting these behaviors can be found in the Configuration.php file (look for canBeUnset() and treatNull/TrueLike()).

Major changes:

 * Removed "enabled" option for profiler config. Profiler is now enabled if its config is true, null or a map.
 * Restore original config structure for validation namespaces. In PHP/YAML, namespaces are defined under annotations as an alternative to false (disabled) and true/null (enabled). For XML, annotation remains a boolean attribute for validation and a one or more optional namespace tags may appear within <app:validation />. During config normalization, namespace tags under validation will be moved to annotations to conform to the PHP/YAML structure (this occurs transparently to the user).
 * Restore behavior for router/templating config sections being optional (as shown in changes to session/validation test fixtures). If either top-level section is unset in the configuration, neither feature will be enabled and the user will no longer receive exceptions due to missing a resource option (router) or engines (templating). Resource/engines will still be properly required if the respective feature is enabled.
 * Remove unused router type option from XML config XSD. Type is only relevant for import statements, so this option is likely useless.

Additional small changes:

 * Added isset()'s, since config options may be unset
 * Wrap registerXxxConfiguration() calls in isset() checks
 * Load translation.xml in configLoad(), since it's always required
 * Default cache_warmer value (!kernel.debug) is determined via Configuration class

Things to be fixed:

 * Configuration\Builder doesn't seem to respect isRequired() and requiresAtLeastOneElement() (or I haven't set it properly); this should replace the need for FrameworkExtension to throw exceptions for bad router/templating configs
 * The config nodes for session options don't have the "pdo." prefix, as dots are not allowed in node names. To preserve BC for now, the "pdo." prefix is still allowed (and mandated by XSD) in configuration files. In the future, we may just want to do away with the "pdo." prefix.
 * Translator has an "enabled" option. If there's no use case for setting "fallback" independently (when "enabled" is false), perhaps "enabled" should be removed entirely and translator should function like profiler currently does.
 * Profiler matcher merging might need to be adjusted so multiple configs simply overwrite matcher instead of merging its array keys.
  • Loading branch information
jmikola authored and fabpot committed Feb 6, 2011
1 parent 2b256a0 commit 099b9de
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 308 deletions.
@@ -0,0 +1,220 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;

/**
* FrameworkExtension configuration structure.
*
* @author Jeremy Mikola <jmikola@gmail.com>
*/
class Configuration
{
/**
* Generates the configuration tree.
*
* @param boolean $kernelDebug The kernel.debug DIC parameter
* @return \Symfony\Component\DependencyInjection\Configuration\NodeInterface
*/
public function getConfigTree($kernelDebug)
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('app:config', 'array');

$rootNode
->scalarNode('cache_warmer')->defaultValue(!$kernelDebug)->end()
->scalarNode('charset')->end()
->scalarNode('document_root')->end()
->scalarNode('error_handler')->end()
->scalarNode('ide')->end()
->booleanNode('test')->end()
;

$this->addCsrfProtectionSection($rootNode);
$this->addEsiSection($rootNode);
$this->addProfilerSection($rootNode);
$this->addRouterSection($rootNode);
$this->addSessionSection($rootNode);
$this->addTemplatingSection($rootNode);
$this->addTranslatorSection($rootNode);
$this->addValidationSection($rootNode);

return $treeBuilder->buildTree();
}

private function addCsrfProtectionSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('csrf_protection')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->booleanNode('enabled')->end()
->scalarNode('field_name')->end()
->scalarNode('secret')->end()
->end()
;
}

private function addEsiSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('esi')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
->booleanNode('enabled')->end()
->end()
;
}

private function addProfilerSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('profiler')
->canBeUnset()
->treatNullLike(array())
->treatTrueLike(array())
->booleanNode('only_exceptions')->end()
->arrayNode('matcher')
->canBeUnset()
->scalarNode('ip')->end()
->scalarNode('path')->end()
->scalarNode('service')->end()
->end()
->end()
;
}

private function addRouterSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('router')
->canBeUnset()
->scalarNode('cache_warmer')->end()
->scalarNode('resource')->isRequired()->end()
->end()
;
}

private function addSessionSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('session')
->canBeUnset()
->treatNullLike(array())
->treatTrueLike(array())
// Strip "pdo." prefix from option keys, since dots cannot appear in node names
->beforeNormalization()
->ifArray()
->then(function($v){
foreach ($v as $key => $value) {
if (0 === strncmp('pdo.', $key, 4)) {
$v[substr($key, 4)] = $value;
unset($v[$key]);
}
}
return $v;
})
->end()
->booleanNode('auto_start')->end()
->scalarNode('class')->end()
->scalarNode('default_locale')->end()
->scalarNode('storage_id')->defaultValue('native')->end()
// NativeSessionStorage options
->scalarNode('name')->end()
->scalarNode('lifetime')->end()
->scalarNode('path')->end()
->scalarNode('domain')->end()
->booleanNode('secure')->end()
->booleanNode('httponly')->end()
// PdoSessionStorage options
->scalarNode('db_table')->end()
->scalarNode('db_id_col')->end()
->scalarNode('db_data_col')->end()
->scalarNode('db_time_col')->end()
->end()
;
}

private function addTemplatingSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('templating')
->canBeUnset()
->scalarNode('assets_version')->end()
->scalarNode('assets_base_urls')->end()
->scalarNode('cache')->end()
->scalarNode('cache_warmer')->end()
->fixXmlConfig('engine')
->arrayNode('engines')
->requiresAtLeastOneElement()
->beforeNormalization()
->ifTrue(function($v){ return !is_array($v); })
->then(function($v){ return array($v); })
->end()
->prototype('scalar')
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && isset($v['id']); })
->then(function($v){ return $v['id']; })
->end()
->end()
->end()
->fixXmlConfig('loader')
->arrayNode('loaders')
->beforeNormalization()
->ifTrue(function($v){ return !is_array($v); })
->then(function($v){ return array($v); })
->end()
->prototype('scalar')->end()
->end()
->end()
;
}

private function addTranslatorSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('translator')
->canBeUnset()
->booleanNode('enabled')->defaultTrue()->end()
->scalarNode('fallback')->end()
->end()
;
}

private function addValidationSection(NodeBuilder $rootNode)
{
$rootNode
->arrayNode('validation')
->canBeUnset()
// For XML, namespace is a child of validation, so it must be moved under annotations
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && !empty($v['annotations']) && !empty($v['namespace']); })
->then(function($v){
$v['annotations'] = array('namespace' => $v['namespace']);
return $v;
})

This comment has been minimized.

Copy link
@weaverryan

weaverryan Mar 12, 2011

Member

So if I'm understanding this correctly, we basically allow the namespace key to be beneath annotations (where it belongs) or just sitting beneath validation? If I'm reading that right, I'd propose that we remove this and make the configuration more consistent.

We originally made these Configuration classes to be true to how the configuration was originally built, but I think removing these "extra" ways to specify things will help things be more transparent.

This comment has been minimized.

Copy link
@stof

stof Mar 12, 2011

Member

this normalization rule was written for the way the XML looks as annotations is an attribute of the node in the XSD.

This comment has been minimized.

Copy link
@jmikola

jmikola Mar 12, 2011

Author Contributor

In the interest of consistent configuration, we'd probably want to drop the dual-nature of annotations in PHP and YAML. It can either be boolean true or have namespaces (an associative array of prefixes => namespace paths) under it.

In this case, I think the XML format is most clear. Annotations is simply an attribute (with only a boolean value) and namespaces are defined under the validation tag. No reason that can't also be done for PHP and YAML as well.

->end()
->booleanNode('enabled')->end()
->arrayNode('annotations')
->canBeUnset()
->treatNullLike(array())
->treatTrueLike(array())
->fixXmlConfig('namespace')
->arrayNode('namespaces')
->containsNameValuePairsWithKeyAttribute('prefix')
->prototype('scalar')
->beforeNormalization()
->ifTrue(function($v) { return is_array($v) && isset($v['namespace']); })
->then(function($v){ return $v['namespace']; })
->end()
->end()
->end()
->end()
->end()
;
}
}

0 comments on commit 099b9de

Please sign in to comment.