Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 | #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 ------- 4f040d7 Add debug:form command
- Loading branch information
Showing
18 changed files
with
917 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]); | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
src/Symfony/Component/Form/Console/Descriptor/Descriptor.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
src/Symfony/Component/Form/Console/Descriptor/JsonDescriptor.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.