Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 20e9af2

Browse filesBrowse files
committed
Add debug:form type option
1 parent b749204 commit 20e9af2
Copy full SHA for 20e9af2

File tree

11 files changed

+270
-86
lines changed
Filter options

11 files changed

+270
-86
lines changed

‎src/Symfony/Component/Form/Command/DebugCommand.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Command/DebugCommand.php
+81-3Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
use Symfony\Component\Console\Output\OutputInterface;
2020
use Symfony\Component\Console\Style\SymfonyStyle;
2121
use Symfony\Component\Form\Console\Helper\DescriptorHelper;
22+
use Symfony\Component\Form\Extension\Core\CoreExtension;
2223
use Symfony\Component\Form\FormRegistryInterface;
24+
use Symfony\Component\Form\FormTypeInterface;
2325

2426
/**
2527
* A console command for retrieving information about form types.
@@ -55,6 +57,7 @@ protected function configure()
5557
$this
5658
->setDefinition(array(
5759
new InputArgument('class', InputArgument::OPTIONAL, 'The form type class'),
60+
new InputArgument('option', InputArgument::OPTIONAL, 'The form type option'),
5861
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'),
5962
))
6063
->setDescription('Displays form type information')
@@ -70,6 +73,10 @@ protected function configure()
7073
7174
The command lists all defined options that contains the given form type, as well as their parents and type extensions.
7275
76+
<info>php %command.full_name% ChoiceType choice_value</info>
77+
78+
The command displays the definition of the given option name.
79+
7380
<info>php %command.full_name% --format=json</info>
7481
7582
The command lists everything in a machine readable json format.
@@ -87,14 +94,42 @@ protected function execute(InputInterface $input, OutputInterface $output)
8794

8895
if (null === $class = $input->getArgument('class')) {
8996
$object = null;
90-
$options['types'] = $this->types;
97+
$options['core_types'] = $this->getCoreTypes();
98+
$options['service_types'] = $this->types;
9199
$options['extensions'] = $this->extensions;
92100
$options['guessers'] = $this->guessers;
101+
foreach ($options as &$list) {
102+
sort($list);
103+
}
93104
} else {
94105
if (!class_exists($class)) {
95106
$class = $this->getFqcnTypeClass($input, $io, $class);
96107
}
97-
$object = $this->formRegistry->getType($class);
108+
$resolvedType = $this->formRegistry->getType($class);
109+
110+
if ($option = $input->getArgument('option')) {
111+
$object = $resolvedType->getOptionsResolver();
112+
113+
if (!$object->isDefined($option)) {
114+
$message = sprintf('Option "%s" is not defined in "%s".', $option, get_class($resolvedType->getInnerType()));
115+
116+
if ($alternatives = $this->findAlternatives($option, $object->getDefinedOptions())) {
117+
if (1 == count($alternatives)) {
118+
$message .= "\n\nDid you mean this?\n ";
119+
} else {
120+
$message .= "\n\nDid you mean one of these?\n ";
121+
}
122+
$message .= implode("\n ", $alternatives);
123+
}
124+
125+
throw new InvalidArgumentException($message);
126+
}
127+
128+
$options['type'] = $resolvedType->getInnerType();
129+
$options['option'] = $option;
130+
} else {
131+
$object = $resolvedType;
132+
}
98133
}
99134

100135
$helper = new DescriptorHelper();
@@ -105,14 +140,27 @@ protected function execute(InputInterface $input, OutputInterface $output)
105140
private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, $shortClassName)
106141
{
107142
$classes = array();
143+
sort($this->namespaces);
108144
foreach ($this->namespaces as $namespace) {
109145
if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) {
110146
$classes[] = $fqcn;
111147
}
112148
}
113149

114150
if (0 === $count = count($classes)) {
115-
throw new InvalidArgumentException(sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces)));
151+
$message = sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces));
152+
153+
$allTypes = array_merge($this->getCoreTypes(), $this->types);
154+
if ($alternatives = $this->findAlternatives($shortClassName, $allTypes)) {
155+
if (1 == count($alternatives)) {
156+
$message .= "\n\nDid you mean this?\n ";
157+
} else {
158+
$message .= "\n\nDid you mean one of these?\n ";
159+
}
160+
$message .= implode("\n ", $alternatives);
161+
}
162+
163+
throw new InvalidArgumentException($message);
116164
}
117165
if (1 === $count) {
118166
return $classes[0];
@@ -123,4 +171,34 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, $shor
123171

124172
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]);
125173
}
174+
175+
private function getCoreTypes()
176+
{
177+
$coreExtension = new CoreExtension();
178+
$coreExtensionRefObject = new \ReflectionObject($coreExtension);
179+
$loadTypesRefMethod = $coreExtensionRefObject->getMethod('loadTypes');
180+
$loadTypesRefMethod->setAccessible(true);
181+
$coreTypes = $loadTypesRefMethod->invoke($coreExtension);
182+
$coreTypes = array_map(function (FormTypeInterface $type) { return get_class($type); }, $coreTypes);
183+
sort($coreTypes);
184+
185+
return $coreTypes;
186+
}
187+
188+
private function findAlternatives($name, array $collection)
189+
{
190+
$alternatives = array();
191+
foreach ($collection as $item) {
192+
$lev = levenshtein($name, $item);
193+
if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
194+
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
195+
}
196+
}
197+
198+
$threshold = 1e3;
199+
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
200+
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
201+
202+
return array_keys($alternatives);
203+
}
126204
}

‎src/Symfony/Component/Form/Console/Descriptor/Descriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Console/Descriptor/Descriptor.php
+28-18Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414
use Symfony\Component\Console\Descriptor\DescriptorInterface;
1515
use Symfony\Component\Console\Input\ArrayInput;
1616
use Symfony\Component\Console\Output\OutputInterface;
17-
use Symfony\Component\Console\Style\StyleInterface;
17+
use Symfony\Component\Console\Style\OutputStyle;
1818
use Symfony\Component\Console\Style\SymfonyStyle;
19-
use Symfony\Component\Form\Extension\Core\CoreExtension;
20-
use Symfony\Component\Form\FormTypeInterface;
2119
use Symfony\Component\Form\ResolvedFormTypeInterface;
2220
use Symfony\Component\Form\Util\OptionsResolverWrapper;
2321
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -29,7 +27,7 @@
2927
*/
3028
abstract class Descriptor implements DescriptorInterface
3129
{
32-
/** @var StyleInterface */
30+
/** @var OutputStyle */
3331
protected $output;
3432
protected $type;
3533
protected $ownOptions = array();
@@ -45,7 +43,7 @@ abstract class Descriptor implements DescriptorInterface
4543
*/
4644
public function describe(OutputInterface $output, $object, array $options = array())
4745
{
48-
$this->output = $output instanceof StyleInterface ? $output : new SymfonyStyle(new ArrayInput(array()), $output);
46+
$this->output = $output instanceof OutputStyle ? $output : new SymfonyStyle(new ArrayInput(array()), $output);
4947

5048
switch (true) {
5149
case null === $object:
@@ -54,6 +52,9 @@ public function describe(OutputInterface $output, $object, array $options = arra
5452
case $object instanceof ResolvedFormTypeInterface:
5553
$this->describeResolvedFormType($object, $options);
5654
break;
55+
case $object instanceof OptionsResolver:
56+
$this->describeOption($object, $options);
57+
break;
5758
default:
5859
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
5960
}
@@ -63,19 +64,7 @@ abstract protected function describeDefaults(array $options = array());
6364

6465
abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array());
6566

66-
protected function getCoreTypes()
67-
{
68-
$coreExtension = new CoreExtension();
69-
$coreExtensionRefObject = new \ReflectionObject($coreExtension);
70-
$loadTypesRefMethod = $coreExtensionRefObject->getMethod('loadTypes');
71-
$loadTypesRefMethod->setAccessible(true);
72-
$coreTypes = $loadTypesRefMethod->invoke($coreExtension);
73-
74-
$coreTypes = array_map(function (FormTypeInterface $type) { return get_class($type); }, $coreTypes);
75-
sort($coreTypes);
76-
77-
return $coreTypes;
78-
}
67+
abstract protected function describeOption(OptionsResolver $optionsResolver, array $options = array());
7968

8069
protected function collectOptions(ResolvedFormTypeInterface $type)
8170
{
@@ -113,6 +102,27 @@ protected function collectOptions(ResolvedFormTypeInterface $type)
113102
$this->extensions = array_keys($this->extensions);
114103
}
115104

105+
protected function getOptionDefinition(OptionsResolver $optionsResolver, $option)
106+
{
107+
$refObject = new \ReflectionObject($optionsResolver);
108+
foreach (array('defaults', 'lazy', 'allowedTypes', 'allowedValues', 'normalizers') as $name) {
109+
$property = $refObject->getProperty($name);
110+
$property->setAccessible(true);
111+
$value = $property->getValue($optionsResolver);
112+
if (array_key_exists($option, $value)) {
113+
$definition[$name] = $value[$option];
114+
}
115+
}
116+
$definition['required'] = $optionsResolver->isRequired($option);
117+
118+
if (isset($definition['lazy'])) {
119+
$definition['defaults'] = 1 === count($definition['lazy']) ? $definition['lazy'][0] : $definition['lazy'];
120+
unset($definition['lazy']);
121+
}
122+
123+
return $definition;
124+
}
125+
116126
private function getParentOptionsResolver(ResolvedFormTypeInterface $type)
117127
{
118128
$this->parents[$class = get_class($type->getInnerType())] = array();

‎src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php
+23-2Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Form\Console\Descriptor;
1313

1414
use Symfony\Component\Form\ResolvedFormTypeInterface;
15+
use Symfony\Component\OptionsResolver\OptionsResolver;
1516

1617
/**
1718
* @author Yonel Ceruto <yonelceruto@gmail.com>
@@ -22,8 +23,8 @@ class JsonDescriptor extends Descriptor
2223
{
2324
protected function describeDefaults(array $options = array())
2425
{
25-
$data['builtin_form_types'] = $this->getCoreTypes();
26-
$data['service_form_types'] = array_values(array_diff($options['types'], $data['builtin_form_types']));
26+
$data['builtin_form_types'] = $options['core_types'];
27+
$data['service_form_types'] = array_values(array_diff($options['service_types'], $data['builtin_form_types']));
2728
$data['type_extensions'] = $options['extensions'];
2829
$data['type_guessers'] = $options['guessers'];
2930

@@ -54,6 +55,26 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF
5455
$this->writeData($data, $options);
5556
}
5657

58+
protected function describeOption(OptionsResolver $optionsResolver, array $options = array())
59+
{
60+
$definition = $this->getOptionDefinition($optionsResolver, $options['option']);
61+
62+
$map = array(
63+
'required' => 'required',
64+
'default' => 'defaults',
65+
'allowed_types' => 'allowedTypes',
66+
'allowed_values' => 'allowedValues',
67+
);
68+
foreach ($map as $label => $name) {
69+
if (array_key_exists($name, $definition)) {
70+
$data[$label] = $definition[$name];
71+
}
72+
}
73+
$data['has_normalizer'] = isset($definition['normalizers']);
74+
75+
$this->writeData($data, $options);
76+
}
77+
5778
private function writeData(array $data, array $options)
5879
{
5980
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;

‎src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php
+39-2Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
use Symfony\Component\Console\Helper\TableSeparator;
1515
use Symfony\Component\Form\ResolvedFormTypeInterface;
16+
use Symfony\Component\OptionsResolver\OptionsResolver;
17+
use Symfony\Component\VarDumper\Cloner\VarCloner;
18+
use Symfony\Component\VarDumper\Dumper\CliDumper;
1619

1720
/**
1821
* @author Yonel Ceruto <yonelceruto@gmail.com>
@@ -23,7 +26,7 @@ class TextDescriptor extends Descriptor
2326
{
2427
protected function describeDefaults(array $options = array())
2528
{
26-
$coreTypes = $this->getCoreTypes();
29+
$coreTypes = $options['core_types'];
2730

2831
$this->output->section('Built-in form types (Symfony\Component\Form\Extension\Core\Type)');
2932
$shortClassNames = array_map(function ($fqcn) { return array_slice(explode('\\', $fqcn), -1)[0]; }, $coreTypes);
@@ -32,7 +35,7 @@ protected function describeDefaults(array $options = array())
3235
}
3336

3437
$this->output->section('Service form types');
35-
$this->output->listing(array_diff($options['types'], $coreTypes));
38+
$this->output->listing(array_diff($options['service_types'], $coreTypes));
3639

3740
$this->output->section('Type extensions');
3841
$this->output->listing($options['extensions']);
@@ -94,6 +97,29 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF
9497
}
9598
}
9699

100+
protected function describeOption(OptionsResolver $optionsResolver, array $options = array())
101+
{
102+
$definition = $this->getOptionDefinition($optionsResolver, $options['option']);
103+
104+
$dump = $this->getDumpFunction();
105+
$map = array(
106+
'Required' => 'required',
107+
'Default' => 'defaults',
108+
'Allowed types' => 'allowedTypes',
109+
'Allowed values' => 'allowedValues',
110+
'Normalizer' => 'normalizers',
111+
);
112+
$rows = array();
113+
foreach ($map as $label => $name) {
114+
$rows[] = array("<info>$label</info>", array_key_exists($name, $definition) ? $dump($definition[$name]) : '-');
115+
$rows[] = new TableSeparator();
116+
}
117+
array_pop($rows);
118+
119+
$this->output->title(sprintf('%s (%s)', get_class($options['type']), $options['option']));
120+
$this->output->table(array(), $rows);
121+
}
122+
97123
private function normalizeAndSortOptionsColumns(array $options)
98124
{
99125
foreach ($options as $group => &$opts) {
@@ -125,4 +151,15 @@ private function normalizeAndSortOptionsColumns(array $options)
125151

126152
return $options;
127153
}
154+
155+
private function getDumpFunction()
156+
{
157+
$cloner = new VarCloner();
158+
$dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
159+
$dumper->setColors($this->output->isDecorated());
160+
161+
return function ($value) use ($dumper, $cloner) {
162+
return rtrim($dumper->dump($cloner->cloneVar($value), true));
163+
};
164+
}
128165
}

‎src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Form\Extension\Core\Type\FormType;
1919
use Symfony\Component\Form\FormRegistryInterface;
2020
use Symfony\Component\Form\ResolvedFormTypeInterface;
21+
use Symfony\Component\OptionsResolver\OptionsResolver;
2122

2223
class DebugCommandTest extends TestCase
2324
{
@@ -39,6 +40,15 @@ public function testDebugSingleFormType()
3940
$this->assertContains('Symfony\Component\Form\Extension\Core\Type\FormType (Block prefix: "form")', $tester->getDisplay());
4041
}
4142

43+
public function testDebugFormTypeOption()
44+
{
45+
$tester = $this->createCommandTester();
46+
$ret = $tester->execute(array('class' => 'FormType', 'option' => 'method'), array('decorated' => false));
47+
48+
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
49+
$this->assertContains('Symfony\Component\Form\Extension\Core\Type\FormType (method)', $tester->getDisplay());
50+
}
51+
4252
/**
4353
* @expectedException \InvalidArgumentException
4454
*/
@@ -68,6 +78,13 @@ private function createCommandTester()
6878
->method('getTypeExtensions')
6979
->willReturn(array())
7080
;
81+
$optionsResolver = new OptionsResolver();
82+
$optionsResolver->setDefault('method', 'POST');
83+
$resolvedFormType
84+
->expects($this->any())
85+
->method('getOptionsResolver')
86+
->willReturn($optionsResolver)
87+
;
7188

7289
$formRegistry = $this->getMockBuilder(FormRegistryInterface::class)->getMock();
7390
$formRegistry

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.