Skip to content

Commit f39a253

Browse files
committed
feature #43598 [Console] add suggestions for debug commands: firewall, form, messenger, router (IonBazan)
This PR was merged into the 5.4 branch. Discussion ---------- [Console] add suggestions for debug commands: firewall, form, messenger, router | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #43594 | License | MIT | Doc PR | - Adding Bash completion for following commands: - `debug:firewall` - `debug:form` - `debug:messenger` - `debug:router` ~Waiting for #43596 to be merged first as it adds testing utilities and `DescriptorHelper::getFormats()`.~ Commits ------- eaf9461722 add suggestions for debug:firewall, debug:form, debug:messenger, debug:router
2 parents 521ff41 + dd6f2c3 commit f39a253

File tree

2 files changed

+153
-13
lines changed

2 files changed

+153
-13
lines changed

Command/DebugCommand.php

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace Symfony\Component\Form\Command;
1313

1414
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Completion\CompletionInput;
16+
use Symfony\Component\Console\Completion\CompletionSuggestions;
1517
use Symfony\Component\Console\Exception\InvalidArgumentException;
1618
use Symfony\Component\Console\Input\InputArgument;
1719
use Symfony\Component\Console\Input\InputInterface;
@@ -159,19 +161,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
159161

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

176166
if (0 === $count = \count($classes)) {
177167
$message = sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces));
@@ -198,6 +188,25 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, strin
198188
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]);
199189
}
200190

191+
private function getFqcnTypeClasses(string $shortClassName): array
192+
{
193+
$classes = [];
194+
sort($this->namespaces);
195+
foreach ($this->namespaces as $namespace) {
196+
if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) {
197+
$classes[] = $fqcn;
198+
} elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName))) {
199+
$classes[] = $fqcn;
200+
} elseif (class_exists($fqcn = $namespace.'\\'.ucfirst($shortClassName).'Type')) {
201+
$classes[] = $fqcn;
202+
} elseif (str_ends_with($shortClassName, 'type') && class_exists($fqcn = $namespace.'\\'.ucfirst(substr($shortClassName, 0, -4).'Type'))) {
203+
$classes[] = $fqcn;
204+
}
205+
}
206+
207+
return $classes;
208+
}
209+
201210
private function getCoreTypes(): array
202211
{
203212
$coreExtension = new CoreExtension();
@@ -242,4 +251,42 @@ private function findAlternatives(string $name, array $collection): array
242251

243252
return array_keys($alternatives);
244253
}
254+
255+
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
256+
{
257+
if ($input->mustSuggestArgumentValuesFor('class')) {
258+
$suggestions->suggestValues(array_merge($this->getCoreTypes(), $this->types));
259+
260+
return;
261+
}
262+
263+
if ($input->mustSuggestArgumentValuesFor('option') && null !== $class = $input->getArgument('class')) {
264+
$this->completeOptions($class, $suggestions);
265+
266+
return;
267+
}
268+
269+
if ($input->mustSuggestOptionValuesFor('format')) {
270+
$helper = new DescriptorHelper();
271+
$suggestions->suggestValues($helper->getFormats());
272+
}
273+
}
274+
275+
private function completeOptions(string $class, CompletionSuggestions $suggestions): void
276+
{
277+
if (!class_exists($class) || !is_subclass_of($class, FormTypeInterface::class)) {
278+
$classes = $this->getFqcnTypeClasses($class);
279+
280+
if (1 === count($classes)) {
281+
$class = $classes[0];
282+
}
283+
}
284+
285+
if (!$this->formRegistry->hasType($class)) {
286+
return;
287+
}
288+
289+
$resolvedType = $this->formRegistry->getType($class);
290+
$suggestions->suggestValues($resolvedType->getOptionsResolver()->getDefinedOptions());
291+
}
245292
}

Tests/Command/DebugCommandTest.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
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;
2022
use Symfony\Component\Form\Extension\Core\Type\TextType;
2123
use Symfony\Component\Form\FormRegistry;
24+
use Symfony\Component\Form\FormTypeInterface;
2225
use Symfony\Component\Form\ResolvedFormTypeFactory;
2326
use Symfony\Component\OptionsResolver\Options;
2427
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -186,6 +189,96 @@ class:%s
186189
, $tester->getDisplay(true));
187190
}
188191

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

0 commit comments

Comments
 (0)