vendor/symfony/form/FormRegistry.php line 94

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <[email protected]>
  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;
  11. use Symfony\Component\Form\Exception\ExceptionInterface;
  12. use Symfony\Component\Form\Exception\InvalidArgumentException;
  13. use Symfony\Component\Form\Exception\LogicException;
  14. use Symfony\Component\Form\Exception\UnexpectedTypeException;
  15. /**
  16. * The central registry of the Form component.
  17. *
  18. * @author Bernhard Schussek <[email protected]>
  19. */
  20. class FormRegistry implements FormRegistryInterface
  21. {
  22. /**
  23. * @var FormExtensionInterface[]
  24. */
  25. private array $extensions = [];
  26. /**
  27. * @var ResolvedFormTypeInterface[]
  28. */
  29. private array $types = [];
  30. private FormTypeGuesserInterface|null|false $guesser = false;
  31. private ResolvedFormTypeFactoryInterface $resolvedTypeFactory;
  32. private array $checkedTypes = [];
  33. /**
  34. * @param FormExtensionInterface[] $extensions
  35. *
  36. * @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface
  37. */
  38. public function __construct(array $extensions, ResolvedFormTypeFactoryInterface $resolvedTypeFactory)
  39. {
  40. foreach ($extensions as $extension) {
  41. if (!$extension instanceof FormExtensionInterface) {
  42. throw new UnexpectedTypeException($extension, FormExtensionInterface::class);
  43. }
  44. }
  45. $this->extensions = $extensions;
  46. $this->resolvedTypeFactory = $resolvedTypeFactory;
  47. }
  48. public function getType(string $name): ResolvedFormTypeInterface
  49. {
  50. if (!isset($this->types[$name])) {
  51. $type = null;
  52. foreach ($this->extensions as $extension) {
  53. if ($extension->hasType($name)) {
  54. $type = $extension->getType($name);
  55. break;
  56. }
  57. }
  58. if (!$type) {
  59. // Support fully-qualified class names
  60. if (!class_exists($name)) {
  61. throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not exist.', $name));
  62. }
  63. if (!is_subclass_of($name, FormTypeInterface::class)) {
  64. throw new InvalidArgumentException(sprintf('Could not load type "%s": class does not implement "Symfony\Component\Form\FormTypeInterface".', $name));
  65. }
  66. $type = new $name();
  67. }
  68. $this->types[$name] = $this->resolveType($type);
  69. }
  70. return $this->types[$name];
  71. }
  72. /**
  73. * Wraps a type into a ResolvedFormTypeInterface implementation and connects it with its parent type.
  74. */
  75. private function resolveType(FormTypeInterface $type): ResolvedFormTypeInterface
  76. {
  77. $parentType = $type->getParent();
  78. $fqcn = $type::class;
  79. if (isset($this->checkedTypes[$fqcn])) {
  80. $types = implode(' > ', array_merge(array_keys($this->checkedTypes), [$fqcn]));
  81. throw new LogicException(sprintf('Circular reference detected for form type "%s" (%s).', $fqcn, $types));
  82. }
  83. $this->checkedTypes[$fqcn] = true;
  84. $typeExtensions = [];
  85. try {
  86. foreach ($this->extensions as $extension) {
  87. $typeExtensions[] = $extension->getTypeExtensions($fqcn);
  88. }
  89. return $this->resolvedTypeFactory->createResolvedType(
  90. $type,
  91. array_merge([], ...$typeExtensions),
  92. $parentType ? $this->getType($parentType) : null
  93. );
  94. } finally {
  95. unset($this->checkedTypes[$fqcn]);
  96. }
  97. }
  98. public function hasType(string $name): bool
  99. {
  100. if (isset($this->types[$name])) {
  101. return true;
  102. }
  103. try {
  104. $this->getType($name);
  105. } catch (ExceptionInterface) {
  106. return false;
  107. }
  108. return true;
  109. }
  110. public function getTypeGuesser(): ?FormTypeGuesserInterface
  111. {
  112. if (false === $this->guesser) {
  113. $guessers = [];
  114. foreach ($this->extensions as $extension) {
  115. $guesser = $extension->getTypeGuesser();
  116. if ($guesser) {
  117. $guessers[] = $guesser;
  118. }
  119. }
  120. $this->guesser = $guessers ? new FormTypeGuesserChain($guessers) : null;
  121. }
  122. return $this->guesser;
  123. }
  124. public function getExtensions(): array
  125. {
  126. return $this->extensions;
  127. }
  128. }