Skip to content

Commit 69d5260

Browse files
committed
Add debug:form command
1 parent 736f0d0 commit 69d5260

File tree

18 files changed

+916
-5
lines changed

18 files changed

+916
-5
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+2
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ public function load(array $configs, ContainerBuilder $container)
209209
if (!class_exists('Symfony\Component\Validator\Validation')) {
210210
throw new LogicException('The Validator component is required to use the Form component.');
211211
}
212+
} else {
213+
$container->removeDefinition('Symfony\Component\Form\Command\DebugCommand');
212214
}
213215

214216
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml

+6
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,11 @@
9696
<service id="Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand">
9797
<tag name="console.command" command="lint:yaml" />
9898
</service>
99+
100+
<service id="Symfony\Component\Form\Command\DebugCommand">
101+
<argument type="service" id="form.registry" />
102+
<argument type="collection" /> <!-- All form types namespaces are stored here by FormPass -->
103+
<tag name="console.command" command="debug:form" />
104+
</service>
99105
</services>
100106
</container>

src/Symfony/Bundle/FrameworkBundle/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"symfony/dom-crawler": "~2.8|~3.0|~4.0",
4141
"symfony/polyfill-intl-icu": "~1.0",
4242
"symfony/security": "~2.8|~3.0|~4.0",
43-
"symfony/form": "~3.3|~4.0",
43+
"symfony/form": "~3.4|~4.0",
4444
"symfony/expression-language": "~2.8|~3.0|~4.0",
4545
"symfony/process": "~2.8|~3.0|~4.0",
4646
"symfony/security-core": "~3.2|~4.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Command;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputArgument;
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Input\InputOption;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
use Symfony\Component\Console\Style\SymfonyStyle;
20+
use Symfony\Component\Form\Console\Helper\DescriptorHelper;
21+
use Symfony\Component\Form\FormRegistryInterface;
22+
23+
/**
24+
* A console command for retrieving information about form types.
25+
*
26+
* @author Yonel Ceruto <yonelceruto@gmail.com>
27+
*/
28+
class DebugCommand extends Command
29+
{
30+
private $formRegistry;
31+
private $namespaces;
32+
33+
public function __construct(FormRegistryInterface $formRegistry, array $namespaces = array('Symfony\Component\Form\Extension\Core\Type'))
34+
{
35+
parent::__construct();
36+
37+
$this->formRegistry = $formRegistry;
38+
$this->namespaces = $namespaces;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
protected function configure()
45+
{
46+
$this
47+
->setName('debug:form')
48+
->setDefinition(array(
49+
new InputArgument('class', InputArgument::REQUIRED, 'The form type class'),
50+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'),
51+
))
52+
->setDescription('Displays form type information')
53+
;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
protected function execute(InputInterface $input, OutputInterface $output)
60+
{
61+
$io = new SymfonyStyle($input, $output);
62+
63+
if (!class_exists($class = $input->getArgument('class'))) {
64+
$class = $this->getFqcnTypeClass($input, $io, $class);
65+
}
66+
67+
$object = $this->formRegistry->getType($class);
68+
69+
$helper = new DescriptorHelper();
70+
$options['format'] = $input->getOption('format');
71+
$helper->describe($io, $object, $options);
72+
}
73+
74+
private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, $shortClassName)
75+
{
76+
$classes = array();
77+
foreach ($this->namespaces as $namespace) {
78+
if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) {
79+
$classes[] = $fqcn;
80+
}
81+
}
82+
83+
if (0 === $count = count($classes)) {
84+
throw new \InvalidArgumentException(sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces)));
85+
}
86+
if (1 === $count) {
87+
return $classes[0];
88+
}
89+
if (!$input->isInteractive()) {
90+
throw new \InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\nDid you mean one of these?\n %s", $shortClassName, implode("\n ", $classes)));
91+
}
92+
93+
return $io->choice(sprintf("The type \"%s\" is ambiguous.\n\n Select one of the following form types to display its information:", $shortClassName), $classes, $classes[0]);
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Console\Descriptor;
13+
14+
use Symfony\Component\Console\Descriptor\DescriptorInterface;
15+
use Symfony\Component\Console\Output\OutputInterface;
16+
use Symfony\Component\Console\Style\SymfonyStyle;
17+
use Symfony\Component\Form\ResolvedFormTypeInterface;
18+
use Symfony\Component\Form\Util\OptionsResolverWrapper;
19+
use Symfony\Component\OptionsResolver\OptionsResolver;
20+
21+
/**
22+
* @author Yonel Ceruto <yonelceruto@gmail.com>
23+
*
24+
* @internal
25+
*/
26+
abstract class Descriptor implements DescriptorInterface
27+
{
28+
/**
29+
* @var SymfonyStyle
30+
*/
31+
protected $output;
32+
protected $type;
33+
protected $ownOptions = array();
34+
protected $overriddenOptions = array();
35+
protected $parentOptions = array();
36+
protected $extensionOptions = array();
37+
protected $requiredOptions = array();
38+
protected $parents = array();
39+
protected $extensions = array();
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function describe(OutputInterface $output, $object, array $options = array())
45+
{
46+
$this->output = $output;
47+
48+
switch (true) {
49+
case $object instanceof ResolvedFormTypeInterface:
50+
$this->describeResolvedFormType($object, $options);
51+
break;
52+
default:
53+
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
54+
}
55+
}
56+
57+
abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array());
58+
59+
protected function collectOptions(ResolvedFormTypeInterface $type)
60+
{
61+
$this->parents = array();
62+
$this->extensions = array();
63+
64+
if (null !== $type->getParent()) {
65+
$optionsResolver = clone $this->getParentOptionsResolver($type->getParent());
66+
} else {
67+
$optionsResolver = new OptionsResolver();
68+
}
69+
70+
$type->getInnerType()->configureOptions($ownOptionsResolver = new OptionsResolverWrapper());
71+
$this->ownOptions = array_diff($ownOptionsResolver->getDefinedOptions(), $optionsResolver->getDefinedOptions());
72+
$overriddenOptions = array_intersect(array_merge($ownOptionsResolver->getDefinedOptions(), $ownOptionsResolver->getUndefinedOptions()), $optionsResolver->getDefinedOptions());
73+
74+
$this->parentOptions = array();
75+
foreach ($this->parents as $class => $parentOptions) {
76+
$this->overriddenOptions[$class] = array_intersect($overriddenOptions, $parentOptions);
77+
$this->parentOptions[$class] = array_diff($parentOptions, $overriddenOptions);
78+
}
79+
80+
$type->getInnerType()->configureOptions($optionsResolver);
81+
$this->collectTypeExtensionsOptions($type, $optionsResolver);
82+
$this->extensionOptions = array();
83+
foreach ($this->extensions as $class => $extensionOptions) {
84+
$this->overriddenOptions[$class] = array_intersect($overriddenOptions, $extensionOptions);
85+
$this->extensionOptions[$class] = array_diff($extensionOptions, $overriddenOptions);
86+
}
87+
88+
$this->overriddenOptions = array_filter($this->overriddenOptions);
89+
$this->requiredOptions = $optionsResolver->getRequiredOptions();
90+
91+
$this->parents = array_keys($this->parents);
92+
$this->extensions = array_keys($this->extensions);
93+
}
94+
95+
private function getParentOptionsResolver(ResolvedFormTypeInterface $type)
96+
{
97+
$this->parents[$class = get_class($type->getInnerType())] = array();
98+
99+
if (null !== $type->getParent()) {
100+
$optionsResolver = clone $this->getParentOptionsResolver($type->getParent());
101+
} else {
102+
$optionsResolver = new OptionsResolver();
103+
}
104+
105+
$inheritedOptions = $optionsResolver->getDefinedOptions();
106+
$type->getInnerType()->configureOptions($optionsResolver);
107+
$this->parents[$class] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions);
108+
109+
$this->collectTypeExtensionsOptions($type, $optionsResolver);
110+
111+
return $optionsResolver;
112+
}
113+
114+
private function collectTypeExtensionsOptions(ResolvedFormTypeInterface $type, OptionsResolver $optionsResolver)
115+
{
116+
foreach ($type->getTypeExtensions() as $extension) {
117+
$inheritedOptions = $optionsResolver->getDefinedOptions();
118+
$extension->configureOptions($optionsResolver);
119+
$this->extensions[get_class($extension)] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions);
120+
}
121+
}
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Console\Descriptor;
13+
14+
use Symfony\Component\Form\ResolvedFormTypeInterface;
15+
16+
/**
17+
* @author Yonel Ceruto <yonelceruto@gmail.com>
18+
*
19+
* @internal
20+
*/
21+
class JsonDescriptor extends Descriptor
22+
{
23+
protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array())
24+
{
25+
$this->collectOptions($resolvedFormType);
26+
27+
$formOptions = array(
28+
'own' => $this->ownOptions,
29+
'overridden' => $this->overriddenOptions,
30+
'parent' => $this->parentOptions,
31+
'extension' => $this->extensionOptions,
32+
'required' => $this->requiredOptions,
33+
);
34+
$this->sortOptions($formOptions);
35+
36+
$data = array(
37+
'class' => get_class($resolvedFormType->getInnerType()),
38+
'block_prefix' => $resolvedFormType->getInnerType()->getBlockPrefix(),
39+
'options' => $formOptions,
40+
'parent_types' => $this->parents,
41+
'type_extensions' => $this->extensions,
42+
);
43+
44+
$this->writeData($data, $options);
45+
}
46+
47+
private function writeData(array $data, array $options)
48+
{
49+
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;
50+
$this->output->write(json_encode($data, $flags | JSON_PRETTY_PRINT)."\n");
51+
}
52+
53+
private function sortOptions(array &$options)
54+
{
55+
foreach ($options as &$opts) {
56+
$sorted = false;
57+
foreach ($opts as &$opt) {
58+
if (is_array($opt)) {
59+
sort($opt);
60+
$sorted = true;
61+
}
62+
}
63+
if (!$sorted) {
64+
sort($opts);
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)