Skip to content

Commit

Permalink
feature #24185 [Form] Display general forms information on debug:form…
Browse files Browse the repository at this point in the history
… (yceruto)

This PR was merged into the 3.4 branch.

Discussion
----------

[Form] Display general forms information on debug:form

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

![debug-form-defaults](https://user-images.githubusercontent.com/2028198/30436620-998103ca-993a-11e7-9873-31f042327374.png)

When we run `bin/console debug:form` (without argument) all possible Form Component information is displayed.

Commits
-------

12d1a7f Display form defaults on debug:form
  • Loading branch information
ogizanagi committed Sep 15, 2017
2 parents c7e84cc + 12d1a7f commit b749204
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 14 deletions.
Expand Up @@ -100,6 +100,9 @@
<service id="Symfony\Component\Form\Command\DebugCommand">
<argument type="service" id="form.registry" />
<argument type="collection" /> <!-- All form types namespaces are stored here by FormPass -->
<argument type="collection" /> <!-- All services form types are stored here by FormPass -->
<argument type="collection" /> <!-- All type extensions are stored here by FormPass -->
<argument type="collection" /> <!-- All type guessers are stored here by FormPass -->
<tag name="console.command" command="debug:form" />
</service>
</services>
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/Form/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

3.4.0
-----

* added `DebugCommand`

3.3.0
-----

Expand Down
40 changes: 30 additions & 10 deletions src/Symfony/Component/Form/Command/DebugCommand.php
Expand Up @@ -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;
Expand All @@ -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;
}

/**
Expand All @@ -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 <info>%command.name%</info> command displays information about a form type.
The <info>%command.name%</info> command displays information about form types.
Either the fully-qualified class name or the short class name can be used:
<info>php %command.full_name%</info>
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.
<info>php %command.full_name% --format=json</info>
The command lists everything in a machine readable json format.
EOF
)
;
Expand All @@ -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);
Expand All @@ -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]);
Expand Down
29 changes: 25 additions & 4 deletions src/Symfony/Component/Form/Console/Descriptor/Descriptor.php
Expand Up @@ -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;
Expand All @@ -25,9 +29,7 @@
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var SymfonyStyle
*/
/** @var StyleInterface */
protected $output;
protected $type;
protected $ownOptions = array();
Expand All @@ -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;
Expand All @@ -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();
Expand Down
10 changes: 10 additions & 0 deletions src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php
Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions src/Symfony/Component/Form/Console/Descriptor/TextDescriptor.php
Expand Up @@ -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);
Expand Down
17 changes: 17 additions & 0 deletions src/Symfony/Component/Form/DependencyInjection/FormPass.php
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions src/Symfony/Component/Form/Tests/Command/DebugCommandTest.php
Expand Up @@ -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();
Expand Down
Expand Up @@ -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)
{
Expand All @@ -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(
Expand Down
@@ -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"
]
}

0 comments on commit b749204

Please sign in to comment.