merged branch kbond/config_dump_command (PR #3187)

Commits
-------

4847d3a renamed command
e97af0b code fixes
df94282 [FrameworkBundle] removed unnecessary DebugCommand
fa32885 [SecurityBundle] added configuration info
2f8ad93 [MonologBundle] added configuration info
9757958 [FrameworkBundle] added configuration info
58939f1 [TwigBundle] added configuration docs
8dc40e4 [FrameworkBundle] added config:dump console command

Discussion
----------

Config dump command

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: #1663
Todo: add more config info/examples

[![Build Status](https://secure.travis-ci.org/kbond/symfony.png?branch=config_dump_command)](http://travis-ci.org/kbond/symfony)

This is a config dump command based on the additions of PR #1099.  This was initially part of that PR and there is some discussion there about it (https://github.com/symfony/symfony/pull/1099)

### Usage:

1. dump by root node: ``app/console config:dump framework``
2. dump by bundle name: ``app/console config:dump FrameworkBundle``

A few issues/notes:

* Only dumps to yaml
* Only 1 configuration per bundle (this was brought by @stof here: https://github.com/symfony/symfony/pull/1099#issuecomment-1242993)
* Works out of the box for most bundles but not ones that use a non-standard ``Configuration`` class (such as the assetic bundle).  In this case ``Extension::getConfiguration()`` must be configurated.

I have used it to create some (most) of the config reference docs.  It works fine but I find it somewhat crude, any suggestions to improve it would be appreciated.

---------------------------------------------------------------------------

by kbond at 2012-01-24T21:00:43Z

Few more issues:

1. Should I abstract the logic to a "normalizer" class that converts the Configuration class into a manageable array?  I struggle with this idea because isn't that what ``TreeBuilder`` basically is?
2. @stof made a good point that ``config:dump`` doesn't really describe what this does.  Would dumping your config be useful?  Perhaps ``config:dump framework`` dumps the config for your project while ``config:dump --ref framework`` dumps the default reference?

---------------------------------------------------------------------------

by stof at 2012-01-24T21:18:15Z

@kbond you cannot really dump the config. Part of it does not go through these extensions at all. And it does not make much sense anyway IMO.
the command as is does the right job IMO (i.e. dumping a reference for the extension). But its name should be improved

---------------------------------------------------------------------------

by kbond at 2012-01-24T21:20:51Z

``config:reference`` perhaps?

---------------------------------------------------------------------------

by fabpot at 2012-02-02T10:05:19Z

This command is about displaying the default configuration for a given bundle. So, what about `config:dump-reference`? As I understand, the command name is the last element to figure out before merging, right?

---------------------------------------------------------------------------

by stof at 2012-02-02T10:19:49Z

@fabpot indeed.

---------------------------------------------------------------------------

by stof at 2012-02-02T10:34:16Z

and +1 for ``config:dump-reference``

---------------------------------------------------------------------------

by Tobion at 2012-02-02T12:08:03Z

why not use the words you chose yourself: `config:dump-default`
I think it's more explicit.
This commit is contained in:
Fabien Potencier 2012-02-04 08:01:13 +01:00
commit 79a957be77
6 changed files with 317 additions and 6 deletions

View File

@ -0,0 +1,261 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\BooleanNode;
/**
* A console command for dumping available configuration reference
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class ConfigDumpReferenceCommand extends ContainerDebugCommand
{
protected $output;
/**
* @see Command
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('name', InputArgument::REQUIRED, 'The Bundle or extension alias')
))
->setName('config:dump-reference')
->setDescription('Dumps default configuration for an extension.')
->setHelp(<<<EOF
The <info>config:dump</info> command dumps the default configuration for an extension/bundle.
The extension alias or bundle name can be used:
Example:
<info>%command.name% framework</info>
or
<info>%command.name% FrameworkBundle</info>
EOF
)
;
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
$kernel = $this->getContainer()->get('kernel');
$containerBuilder = $this->getContainerBuilder();
$name = $input->getArgument('name');
$extension = null;
if (preg_match('/Bundle$/', $name)) {
// input is bundle name
$extension = $kernel->getBundle($name)->getContainerExtension();
if (!$extension) {
throw new \LogicException('No extensions with configuration available for "'.$name.'"');
}
$message = 'Default configuration for "'.$name.'"';
} else {
foreach ($kernel->getBundles() as $bundle) {
$extension = $bundle->getContainerExtension();
if ($extension && $extension->getAlias() === $name) {
break;
}
$extension = null;
}
if (!$extension) {
throw new \LogicException('No extension with alias "'.$name.'" is enabled');
}
$message = 'Default configuration for extension with alias: "'.$name.'"';
}
$configuration = $extension->getConfiguration(array(), $containerBuilder);
if (!$configuration) {
throw new \LogicException('The extension with alias "'.$extension->getAlias().
'" does not have it\'s getConfiguration() method setup');
}
$rootNode = $configuration->getConfigTreeBuilder()->buildTree();
$output->writeln($message);
// root node
$this->outputNode($rootNode);
}
/**
* Outputs a single config reference line
*
* @param string $text
* @param int $indent
*/
private function outputLine($text, $indent = 0)
{
$indent = strlen($text) + $indent;
$format = '%'.$indent.'s';
$this->output->writeln(sprintf($format, $text));
}
private function outputArray(array $array, $depth)
{
$is_indexed = array_values($array) === $array;
foreach ($array as $key => $value) {
if (is_array($value)) {
$val = '';
} else {
$val = $value;
}
if ($is_indexed) {
$this->outputLine('- '.$val, $depth * 4);
} else {
$this->outputLine(sprintf('%-20s %s', $key.':', $val), $depth * 4);
}
if (is_array($value)) {
$this->outputArray($value, $depth + 1);
}
}
}
/**
* @param NodeInterface $node
* @param int $depth
*/
private function outputNode(NodeInterface $node, $depth = 0)
{
$comments = array();
$default = '';
$defaultArray = null;
$children = null;
$example = $node->getExample();
// defaults
if ($node instanceof ArrayNode) {
$children = $node->getChildren();
if ($node instanceof PrototypedArrayNode) {
$prototype = $node->getPrototype();
if ($prototype instanceof ArrayNode) {
$children = $prototype->getChildren();
}
// check for attribute as key
if ($key = $node->getKeyAttribute()) {
$keyNode = new ArrayNode($key, $node);
$keyNode->setInfo('Prototype');
// add children
foreach ($children as $childNode) {
$keyNode->addChild($childNode);
}
$children = array($key => $keyNode);
}
}
if (!$children) {
if ($node->hasDefaultValue() && count($defaultArray = $node->getDefaultValue())) {
$default = '';
} elseif (!is_array($example)) {
$default = '[]';
}
}
} else {
$default = '~';
if ($node->hasDefaultValue()) {
$default = $node->getDefaultValue();
if (true === $default) {
$default = 'true';
} elseif (false === $default) {
$default = 'false';
} elseif (null === $default) {
$default = '~';
}
}
}
// required?
if ($node->isRequired()) {
$comments[] = 'Required';
}
// example
if ($example && !is_array($example)) {
$comments[] = 'Example: '.$example;
}
$default = (string) $default != '' ? ' '.$default : '';
$comments = count($comments) ? '# '.implode(', ', $comments) : '';
$text = sprintf('%-20s %s %s', $node->getName().':', $default, $comments);
if ($info = $node->getInfo()) {
$this->outputLine('');
$this->outputLine('# '.$info, $depth * 4);
}
$this->outputLine($text, $depth * 4);
// output defaults
if ($defaultArray) {
$this->outputLine('');
$message = count($defaultArray) > 1 ? 'Defaults' : 'Default';
$this->outputLine('# '.$message.':', $depth * 4 + 4);
$this->outputArray($defaultArray, $depth + 1);
}
if (is_array($example)) {
$this->outputLine('');
$message = count($example) > 1 ? 'Examples' : 'Example';
$this->outputLine('# '.$message.':', $depth * 4 + 4);
$this->outputArray($example, $depth + 1);
}
if ($children) {
foreach ($children as $childNode) {
$this->outputNode($childNode, $depth + 1);
}
}
}
}

View File

@ -183,7 +183,7 @@ EOF
*
* @return ContainerBuilder
*/
private function getContainerBuilder()
protected function getContainerBuilder()
{
if (!$this->getApplication()->getKernel()->isDebug()) {
throw new \LogicException(sprintf('Debug information about the container is only available in debug mode.'));

View File

@ -46,7 +46,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->scalarNode('charset')->end()
->scalarNode('charset')->setInfo('general configuration')->end()
->scalarNode('trust_proxy_headers')->defaultFalse()->end()
->scalarNode('secret')->isRequired()->end()
->scalarNode('ide')->defaultNull()->end()
@ -73,6 +73,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('form')
->setInfo('form configuration')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
@ -98,6 +99,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('esi')
->setInfo('esi configuration')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
@ -114,6 +116,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('profiler')
->setInfo('profiler configuration')
->canBeUnset()
->children()
->booleanNode('only_exceptions')->defaultFalse()->end()
@ -142,6 +145,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('router')
->setInfo('router configuration')
->canBeUnset()
->children()
->scalarNode('resource')->isRequired()->end()
@ -159,6 +163,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('session')
->setInfo('session configuration')
->canBeUnset()
->children()
->booleanNode('auto_start')->defaultFalse()->end()
@ -201,6 +206,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('templating')
->setInfo('templating configuration')
->canBeUnset()
->children()
->scalarNode('assets_version')->defaultValue(null)->end()
@ -251,6 +257,7 @@ class Configuration implements ConfigurationInterface
->fixXmlConfig('engine')
->children()
->arrayNode('engines')
->setExample(array('twig'))
->isRequired()
->requiresAtLeastOneElement()
->beforeNormalization()
@ -314,6 +321,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('translator')
->setInfo('translator configuration')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
@ -331,6 +339,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('validation')
->setInfo('validation configuration')
->canBeUnset()
->treatNullLike(array('enabled' => true))
->treatTrueLike(array('enabled' => true))
@ -349,6 +358,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->arrayNode('annotations')
->setInfo('annotation configuration')
->addDefaultsIfNotSet()
->children()
->scalarNode('cache')->defaultValue('file')->end()

View File

@ -107,6 +107,26 @@ class Configuration implements ConfigurationInterface
->ifTrue(function($v) { return isset($v['debug']); })
->thenInvalid('The "debug" name cannot be used as it is reserved for the handler of the profiler')
->end()
->setExample(array(
'syslog' => array(
'type' => 'stream',
'path' => '/var/log/symfony.log',
'level' => 'ERROR',
'bubble' => 'false',
'formatter' => 'my_formatter',
'processors' => array('some_callable')
),
'main' => array(
'type' => 'fingerscrossed',
'action_level' => 'WARNING',
'buffer_size' => 30,
'handler' => 'custom',
),
'custom' => array(
'type' => 'service',
'id' => 'my_handler'
)
))
->end()
->end()
;

View File

@ -57,8 +57,8 @@ class MainConfiguration implements ConfigurationInterface
$rootNode
->children()
->scalarNode('access_denied_url')->defaultNull()->end()
->scalarNode('session_fixation_strategy')->cannotBeEmpty()->defaultValue('migrate')->end()
->scalarNode('access_denied_url')->defaultNull()->setExample('/foo/error403')->end()
->scalarNode('session_fixation_strategy')->cannotBeEmpty()->setInfo('strategy can be: none, migrate, invalidate')->defaultValue('migrate')->end()
->booleanNode('hide_user_not_found')->defaultTrue()->end()
->booleanNode('always_authenticate_before_granting')->defaultFalse()->end()
->booleanNode('erase_credentials')->defaultTrue()->end()
@ -89,7 +89,7 @@ class MainConfiguration implements ConfigurationInterface
->children()
->arrayNode('acl')
->children()
->scalarNode('connection')->end()
->scalarNode('connection')->setInfo('any name configured in doctrine.dbal section')->end()
->arrayNode('cache')
->addDefaultsIfNotSet()
->children()
@ -290,6 +290,16 @@ class MainConfiguration implements ConfigurationInterface
->fixXmlConfig('provider')
->children()
->arrayNode('providers')
->setExample(array(
'memory' => array(
'name' => 'memory',
'users' => array(
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
'bar' => array('password' => 'bar', 'roles' => '[ROLE_USER, ROLE_ADMIN]')
)
),
'entity' => array('entity' => array('class' => 'SecurityBundle:User', 'property' => 'username'))
))
->disallowNewKeysInSubsequentConfigs()
->isRequired()
->requiresAtLeastOneElement()
@ -340,6 +350,14 @@ class MainConfiguration implements ConfigurationInterface
->fixXmlConfig('encoder')
->children()
->arrayNode('encoders')
->setExample(array(
'Acme\DemoBundle\Entity\User1' => 'sha512',
'Acme\DemoBundle\Entity\User2' => array(
'algorithm' => 'sha512',
'encode_as_base64' => 'true',
'iterations'=> 5000
)
))
->requiresAtLeastOneElement()
->useAttributeAsKey('class')
->prototype('array')

View File

@ -56,6 +56,7 @@ class Configuration implements ConfigurationInterface
->arrayNode('resources')
->addDefaultsIfNotSet()
->defaultValue(array('form_div_layout.html.twig'))
->setExample(array('MyBundle::form.html.twig'))
->validate()
->ifTrue(function($v) { return !in_array('form_div_layout.html.twig', $v); })
->then(function($v){
@ -77,6 +78,7 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('globals')
->useAttributeAsKey('key')
->setExample(array('foo' => '"@bar"', 'pi' => 3.14))
->prototype('array')
->beforeNormalization()
->ifTrue(function($v){ return is_string($v) && 0 === strpos($v, '@'); })
@ -116,7 +118,7 @@ class Configuration implements ConfigurationInterface
$rootNode
->children()
->scalarNode('autoescape')->end()
->scalarNode('base_template_class')->end()
->scalarNode('base_template_class')->setExample('Twig_Template')->end()
->scalarNode('cache')->defaultValue('%kernel.cache_dir%/twig')->end()
->scalarNode('charset')->defaultValue('%kernel.charset%')->end()
->scalarNode('debug')->defaultValue('%kernel.debug%')->end()