From 12d1a7f81064c3669368e3b2453be28769c2085f Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 13 Sep 2017 08:23:26 -0400 Subject: [PATCH] Display form defaults on debug:form --- .../Resources/config/console.xml | 3 ++ src/Symfony/Component/Form/CHANGELOG.md | 5 +++ .../Component/Form/Command/DebugCommand.php | 40 ++++++++++++----- .../Form/Console/Descriptor/Descriptor.php | 29 ++++++++++-- .../Console/Descriptor/JsonDescriptor.php | 10 +++++ .../Console/Descriptor/TextDescriptor.php | 20 +++++++++ .../Form/DependencyInjection/FormPass.php | 17 +++++++ .../Form/Tests/Command/DebugCommandTest.php | 9 ++++ .../Descriptor/AbstractDescriptorTest.php | 22 +++++++++ .../Tests/Fixtures/Descriptor/defaults_1.json | 45 +++++++++++++++++++ .../Tests/Fixtures/Descriptor/defaults_1.txt | 27 +++++++++++ 11 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.json create mode 100644 src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.txt diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 80bc32d3ff22..7dc4ea6d2fba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -100,6 +100,9 @@ + + + diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 1c8936995c22..8af1a324dca9 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.4.0 +----- + + * added `DebugCommand` + 3.3.0 ----- diff --git a/src/Symfony/Component/Form/Command/DebugCommand.php b/src/Symfony/Component/Form/Command/DebugCommand.php index d646ffa15430..d9b3eee83824 100644 --- a/src/Symfony/Component/Form/Command/DebugCommand.php +++ b/src/Symfony/Component/Form/Command/DebugCommand.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -31,13 +32,19 @@ class DebugCommand extends Command private $formRegistry; private $namespaces; + private $types; + private $extensions; + private $guessers; - public function __construct(FormRegistryInterface $formRegistry, array $namespaces = array('Symfony\Component\Form\Extension\Core\Type')) + public function __construct(FormRegistryInterface $formRegistry, array $namespaces = array('Symfony\Component\Form\Extension\Core\Type'), array $types = array(), array $extensions = array(), array $guessers = array()) { parent::__construct(); $this->formRegistry = $formRegistry; $this->namespaces = $namespaces; + $this->types = $types; + $this->extensions = $extensions; + $this->guessers = $guessers; } /** @@ -47,18 +54,25 @@ protected function configure() { $this ->setDefinition(array( - new InputArgument('class', InputArgument::REQUIRED, 'The form type class'), + new InputArgument('class', InputArgument::OPTIONAL, 'The form type class'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'), )) ->setDescription('Displays form type information') ->setHelp(<<<'EOF' -The %command.name% command displays information about a form type. +The %command.name% command displays information about form types. -Either the fully-qualified class name or the short class name can be used: + php %command.full_name% + +The command lists all built-in types, services types, type extensions and guessers currently available. php %command.full_name% Symfony\Component\Form\Extension\Core\Type\ChoiceType php %command.full_name% ChoiceType +The command lists all defined options that contains the given form type, as well as their parents and type extensions. + + php %command.full_name% --format=json + +The command lists everything in a machine readable json format. EOF ) ; @@ -71,12 +85,18 @@ protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); - if (!class_exists($class = $input->getArgument('class'))) { - $class = $this->getFqcnTypeClass($input, $io, $class); + if (null === $class = $input->getArgument('class')) { + $object = null; + $options['types'] = $this->types; + $options['extensions'] = $this->extensions; + $options['guessers'] = $this->guessers; + } else { + if (!class_exists($class)) { + $class = $this->getFqcnTypeClass($input, $io, $class); + } + $object = $this->formRegistry->getType($class); } - $object = $this->formRegistry->getType($class); - $helper = new DescriptorHelper(); $options['format'] = $input->getOption('format'); $helper->describe($io, $object, $options); @@ -92,13 +112,13 @@ private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, $shor } if (0 === $count = count($classes)) { - throw new \InvalidArgumentException(sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces))); + throw new InvalidArgumentException(sprintf("Could not find type \"%s\" into the following namespaces:\n %s", $shortClassName, implode("\n ", $this->namespaces))); } if (1 === $count) { return $classes[0]; } if (!$input->isInteractive()) { - throw new \InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\nDid you mean one of these?\n %s", $shortClassName, implode("\n ", $classes))); + throw new InvalidArgumentException(sprintf("The type \"%s\" is ambiguous.\nDid you mean one of these?\n %s", $shortClassName, implode("\n ", $classes))); } 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]); diff --git a/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php b/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php index af77f1993124..c72a19d7993f 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/Descriptor.php @@ -12,8 +12,12 @@ namespace Symfony\Component\Form\Console\Descriptor; use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Form\Extension\Core\CoreExtension; +use Symfony\Component\Form\FormTypeInterface; use Symfony\Component\Form\ResolvedFormTypeInterface; use Symfony\Component\Form\Util\OptionsResolverWrapper; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -25,9 +29,7 @@ */ abstract class Descriptor implements DescriptorInterface { - /** - * @var SymfonyStyle - */ + /** @var StyleInterface */ protected $output; protected $type; protected $ownOptions = array(); @@ -43,9 +45,12 @@ abstract class Descriptor implements DescriptorInterface */ public function describe(OutputInterface $output, $object, array $options = array()) { - $this->output = $output; + $this->output = $output instanceof StyleInterface ? $output : new SymfonyStyle(new ArrayInput(array()), $output); switch (true) { + case null === $object: + $this->describeDefaults($options); + break; case $object instanceof ResolvedFormTypeInterface: $this->describeResolvedFormType($object, $options); break; @@ -54,8 +59,24 @@ public function describe(OutputInterface $output, $object, array $options = arra } } + abstract protected function describeDefaults(array $options = array()); + abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array()); + protected function getCoreTypes() + { + $coreExtension = new CoreExtension(); + $coreExtensionRefObject = new \ReflectionObject($coreExtension); + $loadTypesRefMethod = $coreExtensionRefObject->getMethod('loadTypes'); + $loadTypesRefMethod->setAccessible(true); + $coreTypes = $loadTypesRefMethod->invoke($coreExtension); + + $coreTypes = array_map(function (FormTypeInterface $type) { return get_class($type); }, $coreTypes); + sort($coreTypes); + + return $coreTypes; + } + protected function collectOptions(ResolvedFormTypeInterface $type) { $this->parents = array(); diff --git a/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php index 638ea7a5ff71..7616d616f144 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php @@ -20,6 +20,16 @@ */ class JsonDescriptor extends Descriptor { + protected function describeDefaults(array $options = array()) + { + $data['builtin_form_types'] = $this->getCoreTypes(); + $data['service_form_types'] = array_values(array_diff($options['types'], $data['builtin_form_types'])); + $data['type_extensions'] = $options['extensions']; + $data['type_guessers'] = $options['guessers']; + + $this->writeData($data, $options); + } + protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array()) { $this->collectOptions($resolvedFormType); diff --git a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php index b12a22bb9e26..d5072f1e9a63 100644 --- a/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php @@ -21,6 +21,26 @@ */ class TextDescriptor extends Descriptor { + protected function describeDefaults(array $options = array()) + { + $coreTypes = $this->getCoreTypes(); + + $this->output->section('Built-in form types (Symfony\Component\Form\Extension\Core\Type)'); + $shortClassNames = array_map(function ($fqcn) { return array_slice(explode('\\', $fqcn), -1)[0]; }, $coreTypes); + for ($i = 0; $i * 5 < count($shortClassNames); ++$i) { + $this->output->writeln(' '.implode(', ', array_slice($shortClassNames, $i * 5, 5))); + } + + $this->output->section('Service form types'); + $this->output->listing(array_diff($options['types'], $coreTypes)); + + $this->output->section('Type extensions'); + $this->output->listing($options['extensions']); + + $this->output->section('Type guessers'); + $this->output->listing($options['guessers']); + } + protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array()) { $this->collectOptions($resolvedFormType); diff --git a/src/Symfony/Component/Form/DependencyInjection/FormPass.php b/src/Symfony/Component/Form/DependencyInjection/FormPass.php index ff1ac8af6065..bed74532d157 100644 --- a/src/Symfony/Component/Form/DependencyInjection/FormPass.php +++ b/src/Symfony/Component/Form/DependencyInjection/FormPass.php @@ -77,6 +77,7 @@ private function processFormTypes(ContainerBuilder $container) if ($container->hasDefinition($this->formDebugCommandService)) { $commandDefinition = $container->getDefinition($this->formDebugCommandService); $commandDefinition->setArgument(1, array_keys($namespaces)); + $commandDefinition->setArgument(2, array_keys($servicesMap)); } return ServiceLocatorTagPass::register($container, $servicesMap); @@ -85,6 +86,7 @@ private function processFormTypes(ContainerBuilder $container) private function processFormTypeExtensions(ContainerBuilder $container) { $typeExtensions = array(); + $typeExtensionsClasses = array(); foreach ($this->findAndSortTaggedServices($this->formTypeExtensionTag, $container) as $reference) { $serviceId = (string) $reference; $serviceDefinition = $container->getDefinition($serviceId); @@ -97,20 +99,35 @@ private function processFormTypeExtensions(ContainerBuilder $container) } $typeExtensions[$extendedType][] = new Reference($serviceId); + $typeExtensionsClasses[] = $serviceDefinition->getClass(); } foreach ($typeExtensions as $extendedType => $extensions) { $typeExtensions[$extendedType] = new IteratorArgument($extensions); } + if ($container->hasDefinition($this->formDebugCommandService)) { + $commandDefinition = $container->getDefinition($this->formDebugCommandService); + $commandDefinition->setArgument(3, $typeExtensionsClasses); + } + return $typeExtensions; } private function processFormTypeGuessers(ContainerBuilder $container) { $guessers = array(); + $guessersClasses = array(); foreach ($container->findTaggedServiceIds($this->formTypeGuesserTag, true) as $serviceId => $tags) { $guessers[] = new Reference($serviceId); + + $serviceDefinition = $container->getDefinition($serviceId); + $guessersClasses[] = $serviceDefinition->getClass(); + } + + if ($container->hasDefinition($this->formDebugCommandService)) { + $commandDefinition = $container->getDefinition($this->formDebugCommandService); + $commandDefinition->setArgument(4, $guessersClasses); } return new IteratorArgument($guessers); diff --git a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php index c2083ea12ce4..610f6197d62c 100644 --- a/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php @@ -21,6 +21,15 @@ class DebugCommandTest extends TestCase { + public function testDebugDefaults() + { + $tester = $this->createCommandTester(); + $ret = $tester->execute(array(), array('decorated' => false)); + + $this->assertEquals(0, $ret, 'Returns 0 in case of success'); + $this->assertContains('Built-in form types', $tester->getDisplay()); + } + public function testDebugSingleFormType() { $tester = $this->createCommandTester(); diff --git a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php index c760e5ecf972..3a5efcefeb68 100644 --- a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -24,6 +24,19 @@ abstract class AbstractDescriptorTest extends TestCase { + /** @dataProvider getDescribeDefaultsTestData */ + public function testDescribeDefaults($object, array $options, $fixtureName) + { + $expectedDescription = $this->getExpectedDescription($fixtureName); + $describedObject = $this->getObjectDescription($object, $options); + + if ('json' === $this->getFormat()) { + $this->assertEquals(json_encode(json_decode($expectedDescription), JSON_PRETTY_PRINT), json_encode(json_decode($describedObject), JSON_PRETTY_PRINT)); + } else { + $this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $describedObject))); + } + } + /** @dataProvider getDescribeResolvedFormTypeTestData */ public function testDescribeResolvedFormType(ResolvedFormTypeInterface $type, array $options, $fixtureName) { @@ -37,6 +50,15 @@ public function testDescribeResolvedFormType(ResolvedFormTypeInterface $type, ar } } + public function getDescribeDefaultsTestData() + { + $options['types'] = array('Symfony\Bridge\Doctrine\Form\Type\EntityType'); + $options['extensions'] = array('Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension'); + $options['guessers'] = array('Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser'); + + yield array(null, $options, 'defaults_1'); + } + public function getDescribeResolvedFormTypeTestData() { $typeExtensions = array( diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.json b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.json new file mode 100644 index 000000000000..565c17601ed1 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.json @@ -0,0 +1,45 @@ +{ + "builtin_form_types": [ + "Symfony\\Component\\Form\\Extension\\Core\\Type\\BirthdayType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\ButtonType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\CollectionType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\CountryType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\CurrencyType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\DateIntervalType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\DateTimeType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\DateType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\LocaleType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\MoneyType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\NumberType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\PercentType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\RadioType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\RangeType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\ResetType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\SubmitType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\TextareaType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\TimeType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\TimezoneType", + "Symfony\\Component\\Form\\Extension\\Core\\Type\\UrlType" + ], + "service_form_types": [ + "Symfony\\Bridge\\Doctrine\\Form\\Type\\EntityType" + ], + "type_extensions": [ + "Symfony\\Component\\Form\\Extension\\Csrf\\Type\\FormTypeCsrfExtension" + ], + "type_guessers": [ + "Symfony\\Component\\Form\\Extension\\Validator\\ValidatorTypeGuesser" + ] +} diff --git a/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.txt b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.txt new file mode 100644 index 000000000000..dd08d5f7a64c --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Fixtures/Descriptor/defaults_1.txt @@ -0,0 +1,27 @@ + +Built-in form types (Symfony\Component\Form\Extension\Core\Type) +---------------------------------------------------------------- + + BirthdayType, ButtonType, CheckboxType, ChoiceType, CollectionType + CountryType, CurrencyType, DateIntervalType, DateTimeType, DateType + EmailType, FileType, FormType, HiddenType, IntegerType + LanguageType, LocaleType, MoneyType, NumberType, PasswordType + PercentType, RadioType, RangeType, RepeatedType, ResetType + SearchType, SubmitType, TextType, TextareaType, TimeType + TimezoneType, UrlType + +Service form types +------------------ + + * Symfony\Bridge\Doctrine\Form\Type\EntityType + +Type extensions +--------------- + + * Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension + +Type guessers +------------- + + * Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser +