Skip to content

Commit 623af59

Browse files
committed
resolve form type's FQCN in suggestions
1 parent e8ae846 commit 623af59

File tree

4 files changed

+171
-19
lines changed

4 files changed

+171
-19
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/RouterDebugCommand.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,13 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti
139139
{
140140
if ($input->mustSuggestArgumentValuesFor('name')) {
141141
$suggestions->suggestValues(array_keys($this->router->getRouteCollection()->all()));
142+
143+
return;
142144
}
143145

144146
if ($input->mustSuggestOptionValuesFor('format')) {
145147
$helper = new DescriptorHelper();
146-
// @todo wait for getFormats() method to be implemented
147-
//$suggestions->suggestValues($helper->getFormats());
148+
$suggestions->suggestValues($helper->getFormats());
148149
}
149150
}
150151
}

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/RouterDebugCommandTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
1313

1414
use Symfony\Bundle\FrameworkBundle\Console\Application;
15+
use Symfony\Component\Console\Tester\CommandCompletionTester;
1516
use Symfony\Component\Console\Tester\CommandTester;
1617

1718
/**
@@ -69,6 +70,33 @@ public function testSearchWithThrow()
6970
$tester->execute(['name' => 'gerard'], ['interactive' => true]);
7071
}
7172

73+
/**
74+
* @dataProvider provideCompletionSuggestions
75+
*/
76+
public function testComplete(array $input, array $expectedSuggestions)
77+
{
78+
$tester = new CommandCompletionTester($this->application->get('debug:router'));
79+
$this->assertSame($expectedSuggestions, $tester->complete($input));
80+
}
81+
82+
public function provideCompletionSuggestions()
83+
{
84+
yield 'option --format' => [
85+
['--format', ''],
86+
['txt', 'xml', 'json', 'md'],
87+
];
88+
89+
yield 'route_name' => [
90+
[''],
91+
[
92+
'routerdebug_session_welcome',
93+
'routerdebug_session_welcome_name',
94+
'routerdebug_session_logout',
95+
'routerdebug_test',
96+
],
97+
];
98+
}
99+
72100
private function createCommandTester(): CommandTester
73101
{
74102
$command = $this->application->get('debug:router');

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

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
162162

163163
private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, string $shortClassName): string
164164
{
165-
$classes = [];
166-
sort($this->namespaces);
167-
foreach ($this->namespaces as $namespace) {
168-
if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) {
169-
$classes[] = $fqcn;
170-
} elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName))) {
171-
$classes[] = $fqcn;
172-
} elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName).'Type')) {
173-
$classes[] = $fqcn;
174-
} elseif (str_ends_with($shortClassName, 'type') && class_exists($fqcn = $namespace.'\\'.ucfirst(substr($shortClassName, 0, -4).'Type'))) {
175-
$classes[] = $fqcn;
176-
}
177-
}
165+
$classes = $this->getFqcnTypeClasses($shortClassName);
178166

179167
if (0 === $count = \count($classes)) {
180168
$message = sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces));
@@ -201,6 +189,25 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, strin
201189
return $io->choice(sprintf("The type \"%s\" is ambiguous.\n\nSelect one of the following form types to display its information:", $shortClassName), $classes, $classes[0]);
202190
}
203191

192+
private function getFqcnTypeClasses(string $shortClassName): array
193+
{
194+
$classes = [];
195+
sort($this->namespaces);
196+
foreach ($this->namespaces as $namespace) {
197+
if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) {
198+
$classes[] = $fqcn;
199+
} elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName))) {
200+
$classes[] = $fqcn;
201+
} elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName).'Type')) {
202+
$classes[] = $fqcn;
203+
} elseif (str_ends_with($shortClassName, 'type') && class_exists($fqcn = $namespace.'\\'.ucfirst(substr($shortClassName, 0, -4).'Type'))) {
204+
$classes[] = $fqcn;
205+
}
206+
}
207+
208+
return $classes;
209+
}
210+
204211
private function getCoreTypes(): array
205212
{
206213
$coreExtension = new CoreExtension();
@@ -249,12 +256,38 @@ private function findAlternatives(string $name, array $collection): array
249256
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
250257
{
251258
if ($input->mustSuggestArgumentValuesFor('class')) {
252-
$suggestions->suggestValues(array_keys($this->types));
259+
$suggestions->suggestValues(array_merge($this->getCoreTypes(), $this->types));
260+
261+
return;
253262
}
254263

255-
if ($input->mustSuggestOptionValuesFor('option') && null !== $class = $input->getArgument('class')) {
256-
$resolvedType = $this->formRegistry->getType($class);
257-
$suggestions->suggestValues($resolvedType->getOptionsResolver()->getDefinedOptions());
264+
if ($input->mustSuggestArgumentValuesFor('option') && null !== $class = $input->getArgument('class')) {
265+
$this->completeOptions($class, $suggestions);
266+
267+
return;
258268
}
269+
270+
if ($input->mustSuggestOptionValuesFor('format')) {
271+
$helper = new DescriptorHelper();
272+
$suggestions->suggestValues($helper->getFormats());
273+
}
274+
}
275+
276+
private function completeOptions(string $class, CompletionSuggestions $suggestions): void
277+
{
278+
if (!class_exists($class) || !is_subclass_of($class, FormTypeInterface::class)) {
279+
$classes = $this->getFqcnTypeClasses($class);
280+
281+
if (1 === count($classes)) {
282+
$class = $classes[0];
283+
}
284+
}
285+
286+
if (!$this->formRegistry->hasType($class)) {
287+
return;
288+
}
289+
290+
$resolvedType = $this->formRegistry->getType($class);
291+
$suggestions->suggestValues($resolvedType->getOptionsResolver()->getDefinedOptions());
259292
}
260293
}

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Console\Application;
1616
use Symfony\Component\Console\Exception\InvalidArgumentException;
17+
use Symfony\Component\Console\Tester\CommandCompletionTester;
1718
use Symfony\Component\Console\Tester\CommandTester;
1819
use Symfony\Component\Form\AbstractType;
1920
use Symfony\Component\Form\Command\DebugCommand;
21+
use Symfony\Component\Form\Extension\Core\CoreExtension;
22+
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
2023
use Symfony\Component\Form\Extension\Core\Type\TextType;
2124
use Symfony\Component\Form\FormRegistry;
25+
use Symfony\Component\Form\FormTypeInterface;
2226
use Symfony\Component\Form\ResolvedFormTypeFactory;
2327
use Symfony\Component\OptionsResolver\Options;
2428
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -186,6 +190,92 @@ class:%s
186190
, $tester->getDisplay(true));
187191
}
188192

193+
/**
194+
* @dataProvider provideCompletionSuggestions
195+
*/
196+
public function testComplete(array $input, array $expectedSuggestions)
197+
{
198+
$formRegistry = new FormRegistry([], new ResolvedFormTypeFactory());
199+
$command = new DebugCommand($formRegistry);
200+
$application = new Application();
201+
$application->add($command);
202+
$tester = new CommandCompletionTester($application->get('debug:form'));
203+
$this->assertSame($expectedSuggestions, $tester->complete($input));
204+
}
205+
206+
public function provideCompletionSuggestions(): iterable
207+
{
208+
yield 'option --format' => [
209+
['--format', ''],
210+
['txt', 'json'],
211+
];
212+
213+
yield 'form_type' => [
214+
[''],
215+
$this->getCoreTypes(),
216+
];
217+
218+
yield 'option for short name' => [
219+
['Symfony\\Component\\Form\\Extension\\Core\Type\\ButtonType', ''],
220+
[
221+
'block_name',
222+
'block_prefix',
223+
'disabled',
224+
'label',
225+
'label_format',
226+
'row_attr',
227+
'label_html',
228+
'label_translation_parameters',
229+
'attr_translation_parameters',
230+
'attr',
231+
'translation_domain',
232+
'auto_initialize',
233+
'priority',
234+
],
235+
];
236+
237+
yield 'option for FQCN' => [
238+
['ButtonType', ''],
239+
[
240+
'block_name',
241+
'block_prefix',
242+
'disabled',
243+
'label',
244+
'label_format',
245+
'row_attr',
246+
'label_html',
247+
'label_translation_parameters',
248+
'attr_translation_parameters',
249+
'attr',
250+
'translation_domain',
251+
'auto_initialize',
252+
'priority',
253+
],
254+
];
255+
256+
yield 'option for ambiguous form type' => [
257+
['Type', ''],
258+
[],
259+
];
260+
261+
yield 'option for invalid form type' => [
262+
['NotExistingFormType', ''],
263+
[],
264+
];
265+
}
266+
267+
private function getCoreTypes(): array
268+
{
269+
$coreExtension = new CoreExtension();
270+
$loadTypesRefMethod = (new \ReflectionObject($coreExtension))->getMethod('loadTypes');
271+
$loadTypesRefMethod->setAccessible(true);
272+
$coreTypes = $loadTypesRefMethod->invoke($coreExtension);
273+
$coreTypes = array_map(function (FormTypeInterface $type) { return \get_class($type); }, $coreTypes);
274+
sort($coreTypes);
275+
276+
return $coreTypes;
277+
}
278+
189279
private function createCommandTester(array $namespaces = ['Symfony\Component\Form\Extension\Core\Type'], array $types = [])
190280
{
191281
$formRegistry = new FormRegistry([], new ResolvedFormTypeFactory());

0 commit comments

Comments
 (0)