Descriptor.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Form\Console\Descriptor;
  11. use Symfony\Component\Console\Descriptor\DescriptorInterface;
  12. use Symfony\Component\Console\Input\ArrayInput;
  13. use Symfony\Component\Console\Output\OutputInterface;
  14. use Symfony\Component\Console\Style\OutputStyle;
  15. use Symfony\Component\Console\Style\SymfonyStyle;
  16. use Symfony\Component\Form\ResolvedFormTypeInterface;
  17. use Symfony\Component\Form\Util\OptionsResolverWrapper;
  18. use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector;
  19. use Symfony\Component\OptionsResolver\Exception\NoConfigurationException;
  20. use Symfony\Component\OptionsResolver\OptionsResolver;
  21. /**
  22. * @author Yonel Ceruto <yonelceruto@gmail.com>
  23. *
  24. * @internal
  25. */
  26. abstract class Descriptor implements DescriptorInterface
  27. {
  28. /** @var OutputStyle */
  29. protected $output;
  30. protected $type;
  31. protected $ownOptions = [];
  32. protected $overriddenOptions = [];
  33. protected $parentOptions = [];
  34. protected $extensionOptions = [];
  35. protected $requiredOptions = [];
  36. protected $parents = [];
  37. protected $extensions = [];
  38. /**
  39. * {@inheritdoc}
  40. */
  41. public function describe(OutputInterface $output, $object, array $options = [])
  42. {
  43. $this->output = $output instanceof OutputStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output);
  44. switch (true) {
  45. case null === $object:
  46. $this->describeDefaults($options);
  47. break;
  48. case $object instanceof ResolvedFormTypeInterface:
  49. $this->describeResolvedFormType($object, $options);
  50. break;
  51. case $object instanceof OptionsResolver:
  52. $this->describeOption($object, $options);
  53. break;
  54. default:
  55. throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
  56. }
  57. }
  58. abstract protected function describeDefaults(array $options);
  59. abstract protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedFormType, array $options = []);
  60. abstract protected function describeOption(OptionsResolver $optionsResolver, array $options);
  61. protected function collectOptions(ResolvedFormTypeInterface $type)
  62. {
  63. $this->parents = [];
  64. $this->extensions = [];
  65. if (null !== $type->getParent()) {
  66. $optionsResolver = clone $this->getParentOptionsResolver($type->getParent());
  67. } else {
  68. $optionsResolver = new OptionsResolver();
  69. }
  70. $type->getInnerType()->configureOptions($ownOptionsResolver = new OptionsResolverWrapper());
  71. $this->ownOptions = array_diff($ownOptionsResolver->getDefinedOptions(), $optionsResolver->getDefinedOptions());
  72. $overriddenOptions = array_intersect(array_merge($ownOptionsResolver->getDefinedOptions(), $ownOptionsResolver->getUndefinedOptions()), $optionsResolver->getDefinedOptions());
  73. $this->parentOptions = [];
  74. foreach ($this->parents as $class => $parentOptions) {
  75. $this->overriddenOptions[$class] = array_intersect($overriddenOptions, $parentOptions);
  76. $this->parentOptions[$class] = array_diff($parentOptions, $overriddenOptions);
  77. }
  78. $type->getInnerType()->configureOptions($optionsResolver);
  79. $this->collectTypeExtensionsOptions($type, $optionsResolver);
  80. $this->extensionOptions = [];
  81. foreach ($this->extensions as $class => $extensionOptions) {
  82. $this->overriddenOptions[$class] = array_intersect($overriddenOptions, $extensionOptions);
  83. $this->extensionOptions[$class] = array_diff($extensionOptions, $overriddenOptions);
  84. }
  85. $this->overriddenOptions = array_filter($this->overriddenOptions);
  86. $this->parentOptions = array_filter($this->parentOptions);
  87. $this->extensionOptions = array_filter($this->extensionOptions);
  88. $this->requiredOptions = $optionsResolver->getRequiredOptions();
  89. $this->parents = array_keys($this->parents);
  90. $this->extensions = array_keys($this->extensions);
  91. }
  92. protected function getOptionDefinition(OptionsResolver $optionsResolver, string $option)
  93. {
  94. $definition = [];
  95. if ($info = $optionsResolver->getInfo($option)) {
  96. $definition = [
  97. 'info' => $info,
  98. ];
  99. }
  100. $definition += [
  101. 'required' => $optionsResolver->isRequired($option),
  102. 'deprecated' => $optionsResolver->isDeprecated($option),
  103. ];
  104. $introspector = new OptionsResolverIntrospector($optionsResolver);
  105. $map = [
  106. 'default' => 'getDefault',
  107. 'lazy' => 'getLazyClosures',
  108. 'allowedTypes' => 'getAllowedTypes',
  109. 'allowedValues' => 'getAllowedValues',
  110. 'normalizers' => 'getNormalizers',
  111. 'deprecation' => 'getDeprecation',
  112. ];
  113. foreach ($map as $key => $method) {
  114. try {
  115. $definition[$key] = $introspector->{$method}($option);
  116. } catch (NoConfigurationException $e) {
  117. // noop
  118. }
  119. }
  120. if (isset($definition['deprecation']) && isset($definition['deprecation']['message']) && \is_string($definition['deprecation']['message'])) {
  121. $definition['deprecationMessage'] = strtr($definition['deprecation']['message'], ['%name%' => $option]);
  122. $definition['deprecationPackage'] = $definition['deprecation']['package'];
  123. $definition['deprecationVersion'] = $definition['deprecation']['version'];
  124. }
  125. return $definition;
  126. }
  127. protected function filterOptionsByDeprecated(ResolvedFormTypeInterface $type)
  128. {
  129. $deprecatedOptions = [];
  130. $resolver = $type->getOptionsResolver();
  131. foreach ($resolver->getDefinedOptions() as $option) {
  132. if ($resolver->isDeprecated($option)) {
  133. $deprecatedOptions[] = $option;
  134. }
  135. }
  136. $filterByDeprecated = function (array $options) use ($deprecatedOptions) {
  137. foreach ($options as $class => $opts) {
  138. if ($deprecated = array_intersect($deprecatedOptions, $opts)) {
  139. $options[$class] = $deprecated;
  140. } else {
  141. unset($options[$class]);
  142. }
  143. }
  144. return $options;
  145. };
  146. $this->ownOptions = array_intersect($deprecatedOptions, $this->ownOptions);
  147. $this->overriddenOptions = $filterByDeprecated($this->overriddenOptions);
  148. $this->parentOptions = $filterByDeprecated($this->parentOptions);
  149. $this->extensionOptions = $filterByDeprecated($this->extensionOptions);
  150. }
  151. private function getParentOptionsResolver(ResolvedFormTypeInterface $type): OptionsResolver
  152. {
  153. $this->parents[$class = \get_class($type->getInnerType())] = [];
  154. if (null !== $type->getParent()) {
  155. $optionsResolver = clone $this->getParentOptionsResolver($type->getParent());
  156. } else {
  157. $optionsResolver = new OptionsResolver();
  158. }
  159. $inheritedOptions = $optionsResolver->getDefinedOptions();
  160. $type->getInnerType()->configureOptions($optionsResolver);
  161. $this->parents[$class] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions);
  162. $this->collectTypeExtensionsOptions($type, $optionsResolver);
  163. return $optionsResolver;
  164. }
  165. private function collectTypeExtensionsOptions(ResolvedFormTypeInterface $type, OptionsResolver $optionsResolver)
  166. {
  167. foreach ($type->getTypeExtensions() as $extension) {
  168. $inheritedOptions = $optionsResolver->getDefinedOptions();
  169. $extension->configureOptions($optionsResolver);
  170. $this->extensions[\get_class($extension)] = array_diff($optionsResolver->getDefinedOptions(), $inheritedOptions);
  171. }
  172. }
  173. }