Show deprecated options definition on debug:form command

This commit is contained in:
Yonel Ceruto 2018-06-20 16:49:11 -04:00
parent a31f4aa2e9
commit 87c209d741
15 changed files with 313 additions and 47 deletions

View File

@ -58,6 +58,7 @@ class DebugCommand extends Command
->setDefinition(array(
new InputArgument('class', InputArgument::OPTIONAL, 'The form type class'),
new InputArgument('option', InputArgument::OPTIONAL, 'The form type option'),
new InputOption('show-deprecated', null, InputOption::VALUE_NONE, 'Display deprecated options in form types'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'),
))
->setDescription('Displays form type information')
@ -66,15 +67,23 @@ The <info>%command.name%</info> command displays information about form types.
<info>php %command.full_name%</info>
The command lists all built-in types, services types, type extensions and guessers currently available.
The command lists all built-in types, services types, type extensions and
guessers currently available.
<info>php %command.full_name% Symfony\Component\Form\Extension\Core\Type\ChoiceType</info>
<info>php %command.full_name% ChoiceType</info>
The command lists all defined options that contains the given form type, as well as their parents and type extensions.
The command lists all defined options that contains the given form type,
as well as their parents and type extensions.
<info>php %command.full_name% ChoiceType choice_value</info>
Use the <info>--show-deprecated</info> option to display form types with
deprecated options or the deprecated options of the given form type:
<info>php %command.full_name% --show-deprecated</info>
<info>php %command.full_name% ChoiceType --show-deprecated</info>
The command displays the definition of the given option name.
<info>php %command.full_name% --format=json</info>
@ -96,6 +105,10 @@ EOF
$object = null;
$options['core_types'] = $this->getCoreTypes();
$options['service_types'] = array_values(array_diff($this->types, $options['core_types']));
if ($input->getOption('show-deprecated')) {
$options['core_types'] = $this->filterTypesByDeprecated($options['core_types']);
$options['service_types'] = $this->filterTypesByDeprecated($options['service_types']);
}
$options['extensions'] = $this->extensions;
$options['guessers'] = $this->guessers;
foreach ($options as $k => $list) {
@ -114,7 +127,7 @@ EOF
$message = sprintf('Option "%s" is not defined in "%s".', $option, \get_class($resolvedType->getInnerType()));
if ($alternatives = $this->findAlternatives($option, $object->getDefinedOptions())) {
if (1 == \count($alternatives)) {
if (1 === \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
@ -134,6 +147,7 @@ EOF
$helper = new DescriptorHelper();
$options['format'] = $input->getOption('format');
$options['show_deprecated'] = $input->getOption('show-deprecated');
$helper->describe($io, $object, $options);
}
@ -152,7 +166,7 @@ EOF
$allTypes = array_merge($this->getCoreTypes(), $this->types);
if ($alternatives = $this->findAlternatives($shortClassName, $allTypes)) {
if (1 == \count($alternatives)) {
if (1 === \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
@ -184,6 +198,22 @@ EOF
return $coreTypes;
}
private function filterTypesByDeprecated(array $types): array
{
$typesWithDeprecatedOptions = array();
foreach ($types as $class) {
$optionsResolver = $this->formRegistry->getType($class)->getOptionsResolver();
foreach ($optionsResolver->getDefinedOptions() as $option) {
if ($optionsResolver->isDeprecated($option)) {
$typesWithDeprecatedOptions[] = $class;
break;
}
}
}
return $typesWithDeprecatedOptions;
}
private function findAlternatives($name, array $collection)
{
$alternatives = array();

View File

@ -108,7 +108,10 @@ abstract class Descriptor implements DescriptorInterface
protected function getOptionDefinition(OptionsResolver $optionsResolver, $option)
{
$definition = array('required' => $optionsResolver->isRequired($option));
$definition = array(
'required' => $optionsResolver->isRequired($option),
'deprecated' => $optionsResolver->isDeprecated($option),
);
$introspector = new OptionsResolverIntrospector($optionsResolver);
@ -118,6 +121,7 @@ abstract class Descriptor implements DescriptorInterface
'allowedTypes' => 'getAllowedTypes',
'allowedValues' => 'getAllowedValues',
'normalizer' => 'getNormalizer',
'deprecationMessage' => 'getDeprecationMessage',
);
foreach ($map as $key => $method) {
@ -128,9 +132,41 @@ abstract class Descriptor implements DescriptorInterface
}
}
if (isset($definition['deprecationMessage']) && \is_string($definition['deprecationMessage'])) {
$definition['deprecationMessage'] = strtr($definition['deprecationMessage'], array('%name%' => $option));
}
return $definition;
}
protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type)
{
$deprecatedOptions = array();
$resolver = $type->getOptionsResolver();
foreach ($resolver->getDefinedOptions() as $option) {
if ($resolver->isDeprecated($option)) {
$deprecatedOptions[] = $option;
}
}
$filterByDeprecated = function (array $options) use ($deprecatedOptions) {
foreach ($options as $class => $opts) {
if ($deprecated = array_intersect($deprecatedOptions, $opts)) {
$options[$class] = $deprecated;
} else {
unset($options[$class]);
}
}
return $options;
};
$this->ownOptions = array_intersect($deprecatedOptions, $this->ownOptions);
$this->overriddenOptions = $filterByDeprecated($this->overriddenOptions);
$this->parentOptions = $filterByDeprecated($this->parentOptions);
$this->extensionOptions = $filterByDeprecated($this->extensionOptions);
}
private function getParentOptionsResolver(ResolvedFormTypeInterface $type)
{
$this->parents[$class = \get_class($type->getInnerType())] = array();

View File

@ -25,8 +25,10 @@ class JsonDescriptor extends Descriptor
{
$data['builtin_form_types'] = $options['core_types'];
$data['service_form_types'] = $options['service_types'];
$data['type_extensions'] = $options['extensions'];
$data['type_guessers'] = $options['guessers'];
if (!$options['show_deprecated']) {
$data['type_extensions'] = $options['extensions'];
$data['type_guessers'] = $options['guessers'];
}
$this->writeData($data, $options);
}
@ -35,6 +37,10 @@ class JsonDescriptor extends Descriptor
{
$this->collectOptions($resolvedFormType);
if ($options['show_deprecated']) {
$this->filterOptionsByDeprecated($resolvedFormType);
}
$formOptions = array(
'own' => $this->ownOptions,
'overridden' => $this->overriddenOptions,
@ -59,7 +65,14 @@ class JsonDescriptor extends Descriptor
{
$definition = $this->getOptionDefinition($optionsResolver, $options['option']);
$map = array(
$map = array();
if ($definition['deprecated']) {
$map['deprecated'] = 'deprecated';
if (\is_string($definition['deprecationMessage'])) {
$map['deprecation_message'] = 'deprecationMessage';
}
}
$map += array(
'required' => 'required',
'default' => 'default',
'allowed_types' => 'allowedTypes',
@ -81,7 +94,7 @@ class JsonDescriptor extends Descriptor
private function writeData(array $data, array $options)
{
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;
$flags = $options['json_encoding'] ?? 0;
$this->output->write(json_encode($data, $flags | JSON_PRETTY_PRINT)."\n");
}

View File

@ -27,26 +27,40 @@ class TextDescriptor extends Descriptor
{
protected function describeDefaults(array $options)
{
$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]; }, $options['core_types']);
for ($i = 0; $i * 5 < \count($shortClassNames); ++$i) {
$this->output->writeln(' '.implode(', ', \array_slice($shortClassNames, $i * 5, 5)));
if ($options['core_types']) {
$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]; }, $options['core_types']);
for ($i = 0, $loopsMax = \count($shortClassNames); $i * 5 < $loopsMax; ++$i) {
$this->output->writeln(' '.implode(', ', \array_slice($shortClassNames, $i * 5, 5)));
}
}
$this->output->section('Service form types');
$this->output->listing($options['service_types']);
if ($options['service_types']) {
$this->output->section('Service form types');
$this->output->listing($options['service_types']);
}
$this->output->section('Type extensions');
$this->output->listing($options['extensions']);
if (!$options['show_deprecated']) {
if ($options['extensions']) {
$this->output->section('Type extensions');
$this->output->listing($options['extensions']);
}
$this->output->section('Type guessers');
$this->output->listing($options['guessers']);
if ($options['guessers']) {
$this->output->section('Type guessers');
$this->output->listing($options['guessers']);
}
}
}
protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array())
{
$this->collectOptions($resolvedFormType);
if ($options['show_deprecated']) {
$this->filterOptionsByDeprecated($resolvedFormType);
}
$formOptions = $this->normalizeAndSortOptionsColumns(array_filter(array(
'own' => $this->ownOptions,
'overridden' => $this->overriddenOptions,
@ -62,28 +76,11 @@ class TextDescriptor extends Descriptor
'extension' => 'Extension options',
), $formOptions);
$tableRows = array();
$count = \count(max($formOptions));
for ($i = 0; $i < $count; ++$i) {
$cells = array();
foreach (array_keys($tableHeaders) as $group) {
if (isset($formOptions[$group][$i])) {
$option = $formOptions[$group][$i];
if (\is_string($option) && \in_array($option, $this->requiredOptions)) {
$option .= ' <info>(required)</info>';
}
$cells[] = $option;
} else {
$cells[] = null;
}
}
$tableRows[] = $cells;
}
$this->output->title(sprintf('%s (Block prefix: "%s")', \get_class($resolvedFormType->getInnerType()), $resolvedFormType->getInnerType()->getBlockPrefix()));
$this->output->table($tableHeaders, $tableRows);
if ($formOptions) {
$this->output->table($tableHeaders, $this->buildTableRows($tableHeaders, $formOptions));
}
if ($this->parents) {
$this->output->section('Parent types');
@ -101,7 +98,14 @@ class TextDescriptor extends Descriptor
$definition = $this->getOptionDefinition($optionsResolver, $options['option']);
$dump = $this->getDumpFunction();
$map = array(
$map = array();
if ($definition['deprecated']) {
$map = array(
'Deprecated' => 'deprecated',
'Deprecation message' => 'deprecationMessage',
);
}
$map += array(
'Required' => 'required',
'Default' => 'default',
'Allowed types' => 'allowedTypes',
@ -124,6 +128,25 @@ class TextDescriptor extends Descriptor
$this->output->table(array(), $rows);
}
private function buildTableRows(array $headers, array $options): array
{
$tableRows = array();
$count = \count(max($options));
for ($i = 0; $i < $count; ++$i) {
$cells = array();
foreach (array_keys($headers) as $group) {
$option = $options[$group][$i] ?? null;
if (\is_string($option) && \in_array($option, $this->requiredOptions, true)) {
$option .= ' <info>(required)</info>';
}
$cells[] = $option;
}
$tableRows[] = $cells;
}
return $tableRows;
}
private function normalizeAndSortOptionsColumns(array $options)
{
foreach ($options as $group => $opts) {

View File

@ -16,8 +16,10 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Form\Command\DebugCommand;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\ResolvedFormTypeFactory;
use Symfony\Component\Form\Tests\Console\Descriptor\FooType;
class DebugCommandTest extends TestCase
{
@ -30,6 +32,24 @@ class DebugCommandTest extends TestCase
$this->assertContains('Built-in form types', $tester->getDisplay());
}
public function testDebugDeprecatedDefaults()
{
$tester = $this->createCommandTester(array('Symfony\Component\Form\Tests\Console\Descriptor'), array(TextType::class, FooType::class));
$ret = $tester->execute(array('--show-deprecated' => true), array('decorated' => false));
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
$this->assertSame(<<<TXT
Service form types
------------------
* Symfony\Component\Form\Tests\Console\Descriptor\FooType
TXT
, $tester->getDisplay(true));
}
public function testDebugSingleFormType()
{
$tester = $this->createCommandTester();
@ -117,10 +137,10 @@ TXT
$this->createCommandTester()->execute(array('class' => 'test'));
}
private function createCommandTester(array $namespaces = null)
private function createCommandTester(array $namespaces = array('Symfony\Component\Form\Extension\Core\Type'), array $types = array())
{
$formRegistry = new FormRegistry(array(), new ResolvedFormTypeFactory());
$command = null === $namespaces ? new DebugCommand($formRegistry) : new DebugCommand($formRegistry, $namespaces);
$command = new DebugCommand($formRegistry, $namespaces, $types);
$application = new Application();
$application->add($command);

View File

@ -74,8 +74,13 @@ abstract class AbstractDescriptorTest extends TestCase
$options['extensions'] = array('Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension');
$options['guessers'] = array('Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser');
$options['decorated'] = false;
$options['show_deprecated'] = false;
yield array(null, $options, 'defaults_1');
$options['core_types'] = array();
$options['service_types'] = array(FooType::class);
$options['show_deprecated'] = true;
yield array(null, $options, 'types_with_deprecated_options');
}
public function getDescribeResolvedFormTypeTestData()
@ -83,14 +88,16 @@ abstract class AbstractDescriptorTest extends TestCase
$typeExtensions = array(new FormTypeCsrfExtension(new CsrfTokenManager()));
$parent = new ResolvedFormType(new FormType(), $typeExtensions);
yield array(new ResolvedFormType(new ChoiceType(), array(), $parent), array('decorated' => false), 'resolved_form_type_1');
yield array(new ResolvedFormType(new FormType()), array('decorated' => false), 'resolved_form_type_2');
yield array(new ResolvedFormType(new ChoiceType(), array(), $parent), array('decorated' => false, 'show_deprecated' => false), 'resolved_form_type_1');
yield array(new ResolvedFormType(new FormType()), array('decorated' => false, 'show_deprecated' => false), 'resolved_form_type_2');
yield array(new ResolvedFormType(new FooType(), array(), $parent), array('decorated' => false, 'show_deprecated' => true), 'deprecated_options_of_type');
}
public function getDescribeOptionTestData()
{
$parent = new ResolvedFormType(new FormType());
$options['decorated'] = false;
$options['show_deprecated'] = false;
$resolvedType = new ResolvedFormType(new ChoiceType(), array(), $parent);
$options['type'] = $resolvedType->getInnerType();
@ -104,6 +111,12 @@ abstract class AbstractDescriptorTest extends TestCase
$options['option'] = 'empty_data';
yield array($resolvedType->getOptionsResolver(), $options, 'overridden_option_with_default_closures');
$resolvedType = new ResolvedFormType(new FooType(), array(), $parent);
$options['type'] = $resolvedType->getInnerType();
$options['option'] = 'bar';
$options['show_deprecated'] = true;
yield array($resolvedType->getOptionsResolver(), $options, 'deprecated_option');
}
abstract protected function getDescriptor();
@ -136,6 +149,8 @@ class FooType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired('foo');
$resolver->setDefined('bar');
$resolver->setDeprecated('bar');
$resolver->setDefault('empty_data', function (Options $options, $value) {
$foo = $options['foo'];

View File

@ -0,0 +1,6 @@
{
"deprecated": true,
"deprecation_message": "The option \"bar\" is deprecated.",
"required": false,
"has_normalizer": false
}

View File

@ -0,0 +1,18 @@
Symfony\Component\Form\Tests\Console\Descriptor\FooType (bar)
=============================================================
--------------------- -----------------------------------
Deprecated true
--------------------- -----------------------------------
Deprecation message "The option "bar" is deprecated."
--------------------- -----------------------------------
Required false
--------------------- -----------------------------------
Default -
--------------------- -----------------------------------
Allowed types -
--------------------- -----------------------------------
Allowed values -
--------------------- -----------------------------------
Normalizer -
--------------------- -----------------------------------

View File

@ -0,0 +1,21 @@
{
"class": "Symfony\\Component\\Form\\Tests\\Console\\Descriptor\\FooType",
"block_prefix": "foo",
"options": {
"own": [
"bar"
],
"overridden": [],
"parent": [],
"extension": [],
"required": [
"foo"
]
},
"parent_types": [
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType"
],
"type_extensions": [
"Symfony\\Component\\Form\\Extension\\Csrf\\Type\\FormTypeCsrfExtension"
]
}

View File

@ -0,0 +1,19 @@
Symfony\Component\Form\Tests\Console\Descriptor\FooType (Block prefix: "foo")
=============================================================================
---------
Options
---------
bar
---------
Parent types
------------
* Symfony\Component\Form\Extension\Core\Type\FormType
Type extensions
---------------
* Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension

View File

@ -0,0 +1,6 @@
{
"builtin_form_types": [],
"service_form_types": [
"Symfony\\Component\\Form\\Tests\\Console\\Descriptor\\FooType"
]
}

View File

@ -0,0 +1,4 @@
Service form types
------------------
* Symfony\Component\Form\Tests\Console\Descriptor\FooType

View File

@ -19,7 +19,7 @@
"php": "^7.1.3",
"symfony/event-dispatcher": "~3.4|~4.0",
"symfony/intl": "~3.4|~4.0",
"symfony/options-resolver": "~3.4|~4.0",
"symfony/options-resolver": "~4.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.0",
"symfony/property-access": "~3.4|~4.0"

View File

@ -87,4 +87,14 @@ class OptionsResolverIntrospector
{
return \call_user_func($this->get, 'normalizers', $option, sprintf('No normalizer was set for the "%s" option.', $option));
}
/**
* @return string|\Closure
*
* @throws NoConfigurationException on no configured deprecation
*/
public function getDeprecationMessage(string $option)
{
return \call_user_func($this->get, 'deprecated', $option, sprintf('No deprecation was set for the "%s" option.', $option));
}
}

View File

@ -200,4 +200,49 @@ class OptionsResolverIntrospectorTest extends TestCase
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getNormalizer('foo'));
}
public function testGetDeprecationMessage()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$resolver->setDeprecated('foo', 'The option "foo" is deprecated.');
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('The option "foo" is deprecated.', $debug->getDeprecationMessage('foo'));
}
public function testGetClosureDeprecationMessage()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$resolver->setDeprecated('foo', $closure = function ($value) {});
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame($closure, $debug->getDeprecationMessage('foo'));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\NoConfigurationException
* @expectedExceptionMessage No deprecation was set for the "foo" option.
*/
public function testGetDeprecationMessageThrowsOnNoConfiguredValue()
{
$resolver = new OptionsResolver();
$resolver->setDefined('foo');
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getDeprecationMessage('foo'));
}
/**
* @expectedException \Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
* @expectedExceptionMessage The option "foo" does not exist.
*/
public function testGetDeprecationMessageThrowsOnNotDefinedOption()
{
$resolver = new OptionsResolver();
$debug = new OptionsResolverIntrospector($resolver);
$this->assertSame('bar', $debug->getDeprecationMessage('foo'));
}
}