feature #23694 [Form] Add debug:form command (yceruto)

This PR was merged into the 3.4 branch.

Discussion
----------

[Form] Add debug:form command

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | https://github.com/symfony/symfony/issues/23688
| License       | MIT
| Doc PR        | -

![debug-form](https://user-images.githubusercontent.com/2028198/29007125-c3508cd6-7aca-11e7-91e2-c2b509847db5.png)

A short class name (e.g. `DateType`) can be passed as `class` argument too (the command will try to resolve its FQCN if it's in known form type namespaces).

Commits
-------

4f040d78fe Add debug:form command
This commit is contained in:
Maxime Steinhausser 2017-08-30 18:21:48 +02:00
commit dd3276c2e8
18 changed files with 917 additions and 5 deletions

View File

@ -220,6 +220,8 @@ class FrameworkExtension extends Extension
if (!class_exists('Symfony\Component\Validator\Validation')) {
throw new LogicException('The Validator component is required to use the Form component.');
}
} else {
$container->removeDefinition('Symfony\Component\Form\Command\DebugCommand');
}
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);

View File

@ -96,5 +96,11 @@
<service id="Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand">
<tag name="console.command" command="lint:yaml" />
</service>
<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 -->
<tag name="console.command" command="debug:form" />
</service>
</services>
</container>

View File

@ -40,7 +40,7 @@
"symfony/dom-crawler": "~2.8|~3.0|~4.0",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/security": "~2.8|~3.0|~4.0",
"symfony/form": "~3.3|~4.0",
"symfony/form": "~3.4|~4.0",
"symfony/expression-language": "~2.8|~3.0|~4.0",
"symfony/process": "~2.8|~3.0|~4.0",
"symfony/security-core": "~3.2|~4.0",

View File

@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Form\Console\Helper\DescriptorHelper;
use Symfony\Component\Form\FormRegistryInterface;
/**
* A console command for retrieving information about form types.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class DebugCommand extends Command
{
protected static $defaultName = 'debug:form';
private $formRegistry;
private $namespaces;
public function __construct(FormRegistryInterface $formRegistry, array $namespaces = array('Symfony\Component\Form\Extension\Core\Type'))
{
parent::__construct();
$this->formRegistry = $formRegistry;
$this->namespaces = $namespaces;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('class', InputArgument::REQUIRED, 'The form type class'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt or json)', 'txt'),
))
->setDescription('Displays form type information')
;
}
/**
* {@inheritdoc}
*/
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);
}
$object = $this->formRegistry->getType($class);
$helper = new DescriptorHelper();
$options['format'] = $input->getOption('format');
$helper->describe($io, $object, $options);
}
private function getFqcnTypeClass(InputInterface $input, SymfonyStyle $io, $shortClassName)
{
$classes = array();
foreach ($this->namespaces as $namespace) {
if (class_exists($fqcn = $namespace.'\\'.$shortClassName)) {
$classes[] = $fqcn;
}
}
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)));
}
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)));
}
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]);
}
}

View File

@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Console\Descriptor;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Form\ResolvedFormTypeInterface;
use Symfony\Component\Form\Util\OptionsResolverWrapper;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var SymfonyStyle
*/
protected $output;
protected $type;
protected $ownOptions = array();
protected $overriddenOptions = array();
protected $parentOptions = array();
protected $extensionOptions = array();
protected $requiredOptions = array();
protected $parents = array();
protected $extensions = array();
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = array())
{
$this->output = $output;
switch (true) {
case $object instanceof ResolvedFormTypeInterface:
$this->describeResolvedFormType($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
}
}
abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array());
protected function collectOptions(ResolvedFormTypeInterface $type)
{
$this->parents = array();
$this->extensions = array();
if (null !== $type->getParent()) {
$optionsResolver = clone $this->getParentOptionsResolver($type->getParent());
} else {
$optionsResolver = new OptionsResolver();
}
$type->getInnerType()->configureOptions($ownOptionsResolver = new OptionsResolverWrapper());
$this->ownOptions = array_diff($ownOptionsResolver->getDefinedOptions(), $optionsResolver->getDefinedOptions());
$overriddenOptions = array_intersect(array_merge($ownOptionsResolver->getDefinedOptions(), $ownOptionsResolver->getUndefinedOptions()), $optionsResolver->getDefinedOptions());
$this->parentOptions = array();
foreach ($this->parents as $class => $parentOptions) {
$this->overriddenOptions[$class] = array_intersect($overriddenOptions, $parentOptions);
$this->parentOptions[$class] = array_diff($parentOptions, $overriddenOptions);
}
$type->getInnerType()->configureOptions($optionsResolver);
$this->collectTypeExtensionsOptions($type, $optionsResolver);
$this->extensionOptions = array();
foreach ($this->extensions as $class => $extensionOptions) {
$this->overriddenOptions[$class] = array_intersect($overriddenOptions, $extensionOptions);
$this->extensionOptions[$class] = array_diff($extensionOptions, $overriddenOptions);
}
$this->overriddenOptions = array_filter($this->overriddenOptions);
$this->requiredOptions = $optionsResolver->getRequiredOptions();
$this->parents = array_keys($this->parents);
$this->extensions = array_keys($this->extensions);
}
private function getParentOptionsResolver(ResolvedFormTypeInterface $type)
{
$this->parents[$class = get_class($type->getInnerType())] = array();
if (null !== $type->getParent()) {
$optionsResolver = clone $this->getParentOptionsResolver($type->getParent());
} else {
$optionsResolver = new OptionsResolver();
}
$inheritedOptions = $optionsResolver->getDefinedOptions();
$type->getInnerType()->configureOptions($optionsResolver);
$this->parents[$class] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions);
$this->collectTypeExtensionsOptions($type, $optionsResolver);
return $optionsResolver;
}
private function collectTypeExtensionsOptions(ResolvedFormTypeInterface $type, OptionsResolver $optionsResolver)
{
foreach ($type->getTypeExtensions() as $extension) {
$inheritedOptions = $optionsResolver->getDefinedOptions();
$extension->configureOptions($optionsResolver);
$this->extensions[get_class($extension)] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions);
}
}
}

View File

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Console\Descriptor;
use Symfony\Component\Form\ResolvedFormTypeInterface;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array())
{
$this->collectOptions($resolvedFormType);
$formOptions = array(
'own' => $this->ownOptions,
'overridden' => $this->overriddenOptions,
'parent' => $this->parentOptions,
'extension' => $this->extensionOptions,
'required' => $this->requiredOptions,
);
$this->sortOptions($formOptions);
$data = array(
'class' => get_class($resolvedFormType->getInnerType()),
'block_prefix' => $resolvedFormType->getInnerType()->getBlockPrefix(),
'options' => $formOptions,
'parent_types' => $this->parents,
'type_extensions' => $this->extensions,
);
$this->writeData($data, $options);
}
private function writeData(array $data, array $options)
{
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;
$this->output->write(json_encode($data, $flags | JSON_PRETTY_PRINT)."\n");
}
private function sortOptions(array &$options)
{
foreach ($options as &$opts) {
$sorted = false;
foreach ($opts as &$opt) {
if (is_array($opt)) {
sort($opt);
$sorted = true;
}
}
if (!$sorted) {
sort($opts);
}
}
}
}

View File

@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Console\Descriptor;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Form\ResolvedFormTypeInterface;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
class TextDescriptor extends Descriptor
{
protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = array())
{
$this->collectOptions($resolvedFormType);
$formOptions = $this->normalizeAndSortOptionsColumns(array_filter(array(
'own' => $this->ownOptions,
'overridden' => $this->overriddenOptions,
'parent' => $this->parentOptions,
'extension' => $this->extensionOptions,
)));
// setting headers and column order
$tableHeaders = array_intersect_key(array(
'own' => 'Options',
'overridden' => 'Overridden options',
'parent' => 'Parent options',
'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 ($this->parents) {
$this->output->section('Parent types');
$this->output->listing($this->parents);
}
if ($this->extensions) {
$this->output->section('Type extensions');
$this->output->listing($this->extensions);
}
}
private function normalizeAndSortOptionsColumns(array $options)
{
foreach ($options as $group => &$opts) {
$sorted = false;
foreach ($opts as $class => $opt) {
if (!is_array($opt) || 0 === count($opt)) {
continue;
}
unset($opts[$class]);
if (!$sorted) {
$opts = array();
} else {
$opts[] = null;
}
$opts[] = sprintf('<info>%s</info>', (new \ReflectionClass($class))->getShortName());
$opts[] = new TableSeparator();
sort($opt);
$sorted = true;
$opts = array_merge($opts, $opt);
}
if (!$sorted) {
sort($opts);
}
}
return $options;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Console\Helper;
use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper;
use Symfony\Component\Form\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Form\Console\Descriptor\TextDescriptor;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
class DescriptorHelper extends BaseDescriptorHelper
{
public function __construct()
{
$this
->register('txt', new TextDescriptor())
->register('json', new JsonDescriptor())
;
}
}

View File

@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Form\Command\DebugCommand;
/**
* Adds all services with the tags "form.type", "form.type_extension" and
@ -33,13 +34,15 @@ class FormPass implements CompilerPassInterface
private $formTypeTag;
private $formTypeExtensionTag;
private $formTypeGuesserTag;
private $formDebugCommandService;
public function __construct($formExtensionService = 'form.extension', $formTypeTag = 'form.type', $formTypeExtensionTag = 'form.type_extension', $formTypeGuesserTag = 'form.type_guesser')
public function __construct($formExtensionService = 'form.extension', $formTypeTag = 'form.type', $formTypeExtensionTag = 'form.type_extension', $formTypeGuesserTag = 'form.type_guesser', $formDebugCommandService = DebugCommand::class)
{
$this->formExtensionService = $formExtensionService;
$this->formTypeTag = $formTypeTag;
$this->formTypeExtensionTag = $formTypeExtensionTag;
$this->formTypeGuesserTag = $formTypeGuesserTag;
$this->formDebugCommandService = $formDebugCommandService;
}
public function process(ContainerBuilder $container)
@ -61,12 +64,19 @@ class FormPass implements CompilerPassInterface
{
// Get service locator argument
$servicesMap = array();
$namespaces = array('Symfony\Component\Form\Extension\Core\Type' => true);
// Builds an array with fully-qualified type class names as keys and service IDs as values
foreach ($container->findTaggedServiceIds($this->formTypeTag, true) as $serviceId => $tag) {
// Add form type service to the service locator
$serviceDefinition = $container->getDefinition($serviceId);
$servicesMap[$serviceDefinition->getClass()] = new Reference($serviceId);
$servicesMap[$formType = $serviceDefinition->getClass()] = new Reference($serviceId);
$namespaces[substr($formType, 0, strrpos($formType, '\\'))] = true;
}
if ($container->hasDefinition($this->formDebugCommandService)) {
$commandDefinition = $container->getDefinition($this->formDebugCommandService);
$commandDefinition->setArgument(1, array_keys($namespaces));
}
return ServiceLocatorTagPass::register($container, $servicesMap);

View File

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Component\Form\Command\DebugCommand;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormRegistryInterface;
use Symfony\Component\Form\ResolvedFormTypeInterface;
class DebugCommandTest extends TestCase
{
public function testDebugSingleFormType()
{
$tester = $this->createCommandTester();
$ret = $tester->execute(array('class' => 'FormType'), array('decorated' => false));
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
$this->assertContains('Symfony\Component\Form\Extension\Core\Type\FormType (Block prefix: "form")', $tester->getDisplay());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testDebugInvalidFormType()
{
$this->createCommandTester()->execute(array('class' => 'test'));
}
/**
* @return CommandTester
*/
private function createCommandTester()
{
$resolvedFormType = $this->getMockBuilder(ResolvedFormTypeInterface::class)->getMock();
$resolvedFormType
->expects($this->any())
->method('getParent')
->willReturn(null)
;
$resolvedFormType
->expects($this->any())
->method('getInnerType')
->willReturn(new FormType())
;
$resolvedFormType
->expects($this->any())
->method('getTypeExtensions')
->willReturn(array())
;
$formRegistry = $this->getMockBuilder(FormRegistryInterface::class)->getMock();
$formRegistry
->expects($this->any())
->method('getType')
->will($this->returnValue($resolvedFormType))
;
$command = new DebugCommand($formRegistry);
$application = new Application();
$application->add($command);
return new CommandTester($application->find('debug:form'));
}
}

View File

@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Console\Descriptor;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension;
use Symfony\Component\Form\ResolvedFormType;
use Symfony\Component\Form\ResolvedFormTypeInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
abstract class AbstractDescriptorTest extends TestCase
{
/** @dataProvider getDescribeResolvedFormTypeTestData */
public function testDescribeResolvedFormType(ResolvedFormTypeInterface $type, array $options, $fixtureName)
{
$expectedDescription = $this->getExpectedDescription($fixtureName);
$describedObject = $this->getObjectDescription($type, $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)));
}
}
public function getDescribeResolvedFormTypeTestData()
{
$typeExtensions = array(
new FormTypeCsrfExtension(new CsrfTokenManager()),
);
$parent = new ResolvedFormType(new FormType(), $typeExtensions);
yield array(new ResolvedFormType(new ChoiceType(), array(), $parent), array(), 'resolved_form_type_1');
}
abstract protected function getDescriptor();
abstract protected function getFormat();
private function getObjectDescription($object, array $options = array())
{
$output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
$io = new SymfonyStyle(new ArrayInput(array()), $output);
$this->getDescriptor()->describe($io, $object, $options);
return $output->fetch();
}
private function getExpectedDescription($name)
{
return file_get_contents($this->getFixtureFilename($name));
}
private function getFixtureFilename($name)
{
return sprintf('%s/../../Fixtures/Descriptor/%s.%s', __DIR__, $name, $this->getFormat());
}
}

View File

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Console\Descriptor;
use Symfony\Component\Form\Console\Descriptor\JsonDescriptor;
class JsonDescriptorTest extends AbstractDescriptorTest
{
protected function setUp()
{
putenv('COLUMNS=121');
}
protected function tearDown()
{
putenv('COLUMNS');
}
protected function getDescriptor()
{
return new JsonDescriptor();
}
protected function getFormat()
{
return 'json';
}
}

View File

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Console\Descriptor;
use Symfony\Component\Form\Console\Descriptor\TextDescriptor;
class TextDescriptorTest extends AbstractDescriptorTest
{
protected function setUp()
{
putenv('COLUMNS=121');
}
protected function tearDown()
{
putenv('COLUMNS');
}
protected function getDescriptor()
{
return new TextDescriptor();
}
protected function getFormat()
{
return 'txt';
}
}

View File

@ -14,12 +14,14 @@ namespace Symfony\Component\Form\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\Form\Command\DebugCommand;
use Symfony\Component\Form\DependencyInjection\FormPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormRegistryInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
@ -35,6 +37,15 @@ class FormPassTest extends TestCase
$this->assertFalse($container->hasDefinition('form.extension'));
}
public function testDoNothingIfDebugCommandNotLoaded()
{
$container = $this->createContainerBuilder();
$container->compile();
$this->assertFalse($container->hasDefinition(DebugCommand::class));
}
public function testAddTaggedTypes()
{
$container = $this->createContainerBuilder();
@ -56,6 +67,28 @@ class FormPassTest extends TestCase
);
}
public function testAddTaggedTypesToDebugCommand()
{
$container = $this->createContainerBuilder();
$container->setDefinition('form.extension', $this->createExtensionDefinition());
$container->setDefinition(DebugCommand::class, $this->createDebugCommandDefinition());
$container->register('my.type1', __CLASS__.'_Type1')->addTag('form.type');
$container->register('my.type2', __CLASS__.'_Type2')->addTag('form.type');
$container->compile();
$cmdDefinition = $container->getDefinition(DebugCommand::class);
$this->assertEquals(
array(
'Symfony\Component\Form\Extension\Core\Type',
__NAMESPACE__,
),
$cmdDefinition->getArgument(1)
);
}
/**
* @dataProvider addTaggedTypeExtensionsDataProvider
*/
@ -225,6 +258,18 @@ class FormPassTest extends TestCase
return $definition;
}
private function createDebugCommandDefinition()
{
$definition = new Definition('Symfony\Component\Form\Command\DebugCommand');
$definition->setArguments(array(
$formRegistry = $this->getMockBuilder(FormRegistryInterface::class)->getMock(),
array(),
array('Symfony\Component\Form\Extension\Core\Type'),
));
return $definition;
}
private function createContainerBuilder()
{
$container = new ContainerBuilder();

View File

@ -0,0 +1,68 @@
{
"class": "Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType",
"block_prefix": "choice",
"options": {
"own": [
"choice_attr",
"choice_label",
"choice_loader",
"choice_name",
"choice_translation_domain",
"choice_value",
"choices",
"choices_as_values",
"expanded",
"group_by",
"multiple",
"placeholder",
"preferred_choices"
],
"overridden": {
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType": [
"compound",
"data_class",
"empty_data",
"error_bubbling"
]
},
"parent": {
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType": [
"action",
"attr",
"auto_initialize",
"block_name",
"by_reference",
"data",
"disabled",
"inherit_data",
"label",
"label_attr",
"label_format",
"mapped",
"method",
"post_max_size_message",
"property_path",
"required",
"translation_domain",
"trim",
"upload_max_size_message"
]
},
"extension": {
"Symfony\\Component\\Form\\Extension\\Csrf\\Type\\FormTypeCsrfExtension": [
"csrf_field_name",
"csrf_message",
"csrf_protection",
"csrf_token_id",
"csrf_token_manager"
]
},
"required": []
},
"parent_types": [
"Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType"
],
"type_extensions": [
"Symfony\\Component\\Form\\Extension\\Csrf\\Type\\FormTypeCsrfExtension"
]
}

View File

@ -0,0 +1,40 @@
Symfony\Component\Form\Extension\Core\Type\ChoiceType (Block prefix: "choice")
==============================================================================
--------------------------- -------------------- ------------------------- -----------------------
 Options   Overridden options   Parent options   Extension options 
--------------------------- -------------------- ------------------------- -----------------------
choice_attr FormType FormType FormTypeCsrfExtension
choice_label -------------------- ------------------------- -----------------------
choice_loader compound action csrf_field_name
choice_name data_class attr csrf_message
choice_translation_domain empty_data auto_initialize csrf_protection
choice_value error_bubbling block_name csrf_token_id
choices by_reference csrf_token_manager
choices_as_values data
expanded disabled
group_by inherit_data
multiple label
placeholder label_attr
preferred_choices label_format
mapped
method
post_max_size_message
property_path
required
translation_domain
trim
upload_max_size_message
--------------------------- -------------------- ------------------------- -----------------------
Parent types
------------
* Symfony\Component\Form\Extension\Core\Type\FormType
Type extensions
---------------
* Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension

View File

@ -0,0 +1,91 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Util;
use Symfony\Component\OptionsResolver\Exception\AccessException;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
class OptionsResolverWrapper extends OptionsResolver
{
private $undefined = array();
public function setNormalizer($option, \Closure $normalizer)
{
try {
parent::setNormalizer($option, $normalizer);
} catch (UndefinedOptionsException $e) {
$this->undefined[$option] = true;
}
return $this;
}
public function setAllowedValues($option, $allowedValues)
{
try {
parent::setAllowedValues($option, $allowedValues);
} catch (UndefinedOptionsException $e) {
$this->undefined[$option] = true;
}
return $this;
}
public function addAllowedValues($option, $allowedValues)
{
try {
parent::addAllowedValues($option, $allowedValues);
} catch (UndefinedOptionsException $e) {
$this->undefined[$option] = true;
}
return $this;
}
public function setAllowedTypes($option, $allowedTypes)
{
try {
parent::setAllowedTypes($option, $allowedTypes);
} catch (UndefinedOptionsException $e) {
$this->undefined[$option] = true;
}
return $this;
}
public function addAllowedTypes($option, $allowedTypes)
{
try {
parent::addAllowedTypes($option, $allowedTypes);
} catch (UndefinedOptionsException $e) {
$this->undefined[$option] = true;
}
return $this;
}
public function resolve(array $options = array())
{
throw new AccessException('Resolve options is not supported.');
}
public function getUndefinedOptions()
{
return array_keys($this->undefined);
}
}

View File

@ -19,7 +19,7 @@
"php": "^5.5.9|>=7.0.8",
"symfony/event-dispatcher": "~2.8|~3.0|~4.0",
"symfony/intl": "^2.8.18|^3.2.5|~4.0",
"symfony/options-resolver": "~2.8|~3.0|~4.0",
"symfony/options-resolver": "~3.4|~4.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/property-access": "~2.8|~3.0|~4.0"
},
@ -32,7 +32,8 @@
"symfony/http-kernel": "^3.3.5|~4.0",
"symfony/security-csrf": "~2.8|~3.0|~4.0",
"symfony/translation": "~2.8|~3.0|~4.0",
"symfony/var-dumper": "~3.3|~4.0"
"symfony/var-dumper": "~3.3|~4.0",
"symfony/console": "~3.4|~4.0"
},
"conflict": {
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",