Skip to content

Commit 3cbe9f1

Browse files
committed
Merge branch '5.4' into 6.0
* 5.4: [FrameworkBundle][HttpKernel] Add the ability to enable the profiler using a parameter [FrameworkBundle] Trigger deprecations on stderr instead of using trigger_deprecation call Add PhpStanExtractor [Messenger] allow processing messages in batches [Console] Fix backslash escaping in bash completion Add missing validators translation add suggestions for debug:firewall, debug:form, debug:messenger, debug:router [SecurityBundle] Deprecate not configuring explicitly a provider for custom_authenticators when there is more than one registered provider [Inflector] Fix inflector for "zombies" [Config] Add some cache on SelfCheckingResourceChecker fix AJAX request unit spacing fix ErrorExcception in CacheWarmerAggregate Prevent FormLoginAuthenticator from responding to requests that should be handled by JsonLoginAuthenticator Fix wait duration for fixed window policy Add exact command used to trigger invocation to the completion debug log [Translation] correctly handle intl domains with TargetOperation Allow using param as connection atribute in `*.event_subscriber` and `*.event_listener` tags
2 parents 6ffeab4 + f39a253 commit 3cbe9f1

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
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\Console\Attribute\AsCommand;
1515
use Symfony\Component\Console\Command\Command;
16+
use Symfony\Component\Console\Completion\CompletionInput;
17+
use Symfony\Component\Console\Completion\CompletionSuggestions;
1618
use Symfony\Component\Console\Exception\InvalidArgumentException;
1719
use Symfony\Component\Console\Input\InputArgument;
1820
use Symfony\Component\Console\Input\InputInterface;
@@ -157,19 +159,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
157159

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

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

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

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

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;
@@ -181,6 +184,96 @@ class:%s
181184
, $tester->getDisplay(true));
182185
}
183186

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

0 commit comments

Comments
 (0)