vendor/symfony/form/FormErrorIterator.php line 38

Open in your IDE?
  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;
  11. use Symfony\Component\Form\Exception\BadMethodCallException;
  12. use Symfony\Component\Form\Exception\InvalidArgumentException;
  13. use Symfony\Component\Form\Exception\LogicException;
  14. use Symfony\Component\Form\Exception\OutOfBoundsException;
  15. use Symfony\Component\Validator\ConstraintViolation;
  16. /**
  17. * Iterates over the errors of a form.
  18. *
  19. * This class supports recursive iteration. In order to iterate recursively,
  20. * pass a structure of {@link FormError} and {@link FormErrorIterator} objects
  21. * to the $errors constructor argument.
  22. *
  23. * You can also wrap the iterator into a {@link \RecursiveIteratorIterator} to
  24. * flatten the recursive structure into a flat list of errors.
  25. *
  26. * @author Bernhard Schussek <bschussek@gmail.com>
  27. *
  28. * @template T of FormError|FormErrorIterator
  29. *
  30. * @implements \ArrayAccess<int, T>
  31. * @implements \RecursiveIterator<int, T>
  32. * @implements \SeekableIterator<int, T>
  33. */
  34. class FormErrorIterator implements \RecursiveIterator, \SeekableIterator, \ArrayAccess, \Countable
  35. {
  36. /**
  37. * The prefix used for indenting nested error messages.
  38. */
  39. public const INDENTATION = ' ';
  40. private $form;
  41. /**
  42. * @var list<T>
  43. */
  44. private $errors;
  45. /**
  46. * @param list<T> $errors
  47. *
  48. * @throws InvalidArgumentException If the errors are invalid
  49. */
  50. public function __construct(FormInterface $form, array $errors)
  51. {
  52. foreach ($errors as $error) {
  53. if (!($error instanceof FormError || $error instanceof self)) {
  54. throw new InvalidArgumentException(sprintf('The errors must be instances of "Symfony\Component\Form\FormError" or "%s". Got: "%s".', __CLASS__, get_debug_type($error)));
  55. }
  56. }
  57. $this->form = $form;
  58. $this->errors = $errors;
  59. }
  60. /**
  61. * Returns all iterated error messages as string.
  62. *
  63. * @return string
  64. */
  65. public function __toString()
  66. {
  67. $string = '';
  68. foreach ($this->errors as $error) {
  69. if ($error instanceof FormError) {
  70. $string .= 'ERROR: '.$error->getMessage()."\n";
  71. } else {
  72. /* @var self $error */
  73. $string .= $error->getForm()->getName().":\n";
  74. $string .= self::indent((string) $error);
  75. }
  76. }
  77. return $string;
  78. }
  79. /**
  80. * Returns the iterated form.
  81. *
  82. * @return FormInterface
  83. */
  84. public function getForm()
  85. {
  86. return $this->form;
  87. }
  88. /**
  89. * Returns the current element of the iterator.
  90. *
  91. * @return T An error or an iterator containing nested errors
  92. */
  93. #[\ReturnTypeWillChange]
  94. public function current()
  95. {
  96. return current($this->errors);
  97. }
  98. /**
  99. * Advances the iterator to the next position.
  100. */
  101. #[\ReturnTypeWillChange]
  102. public function next()
  103. {
  104. next($this->errors);
  105. }
  106. /**
  107. * Returns the current position of the iterator.
  108. *
  109. * @return int
  110. */
  111. #[\ReturnTypeWillChange]
  112. public function key()
  113. {
  114. return key($this->errors);
  115. }
  116. /**
  117. * Returns whether the iterator's position is valid.
  118. *
  119. * @return bool
  120. */
  121. #[\ReturnTypeWillChange]
  122. public function valid()
  123. {
  124. return null !== key($this->errors);
  125. }
  126. /**
  127. * Sets the iterator's position to the beginning.
  128. *
  129. * This method detects if errors have been added to the form since the
  130. * construction of the iterator.
  131. */
  132. #[\ReturnTypeWillChange]
  133. public function rewind()
  134. {
  135. reset($this->errors);
  136. }
  137. /**
  138. * Returns whether a position exists in the iterator.
  139. *
  140. * @param int $position The position
  141. *
  142. * @return bool
  143. */
  144. #[\ReturnTypeWillChange]
  145. public function offsetExists($position)
  146. {
  147. return isset($this->errors[$position]);
  148. }
  149. /**
  150. * Returns the element at a position in the iterator.
  151. *
  152. * @param int $position The position
  153. *
  154. * @return T
  155. *
  156. * @throws OutOfBoundsException If the given position does not exist
  157. */
  158. #[\ReturnTypeWillChange]
  159. public function offsetGet($position)
  160. {
  161. if (!isset($this->errors[$position])) {
  162. throw new OutOfBoundsException('The offset '.$position.' does not exist.');
  163. }
  164. return $this->errors[$position];
  165. }
  166. /**
  167. * Unsupported method.
  168. *
  169. * @return void
  170. *
  171. * @throws BadMethodCallException
  172. */
  173. #[\ReturnTypeWillChange]
  174. public function offsetSet($position, $value)
  175. {
  176. throw new BadMethodCallException('The iterator doesn\'t support modification of elements.');
  177. }
  178. /**
  179. * Unsupported method.
  180. *
  181. * @return void
  182. *
  183. * @throws BadMethodCallException
  184. */
  185. #[\ReturnTypeWillChange]
  186. public function offsetUnset($position)
  187. {
  188. throw new BadMethodCallException('The iterator doesn\'t support modification of elements.');
  189. }
  190. /**
  191. * Returns whether the current element of the iterator can be recursed
  192. * into.
  193. *
  194. * @return bool
  195. */
  196. #[\ReturnTypeWillChange]
  197. public function hasChildren()
  198. {
  199. return current($this->errors) instanceof self;
  200. }
  201. /**
  202. * @return self
  203. */
  204. #[\ReturnTypeWillChange]
  205. public function getChildren()
  206. {
  207. if (!$this->hasChildren()) {
  208. trigger_deprecation('symfony/form', '5.4', 'Calling "%s()" if the current element is not iterable is deprecated, call "%s" to get the current element.', __METHOD__, self::class.'::current()');
  209. // throw new LogicException(sprintf('The current element is not iterable. Use "%s" to get the current element.', self::class.'::current()'));
  210. }
  211. /** @var self $children */
  212. $children = current($this->errors);
  213. return $children;
  214. }
  215. /**
  216. * Returns the number of elements in the iterator.
  217. *
  218. * Note that this is not the total number of errors, if the constructor
  219. * parameter $deep was set to true! In that case, you should wrap the
  220. * iterator into a {@link \RecursiveIteratorIterator} with the standard mode
  221. * {@link \RecursiveIteratorIterator::LEAVES_ONLY} and count the result.
  222. *
  223. * $iterator = new \RecursiveIteratorIterator($form->getErrors(true));
  224. * $count = count(iterator_to_array($iterator));
  225. *
  226. * Alternatively, set the constructor argument $flatten to true as well.
  227. *
  228. * $count = count($form->getErrors(true, true));
  229. *
  230. * @return int
  231. */
  232. #[\ReturnTypeWillChange]
  233. public function count()
  234. {
  235. return \count($this->errors);
  236. }
  237. /**
  238. * Sets the position of the iterator.
  239. *
  240. * @param int $position The new position
  241. *
  242. * @return void
  243. *
  244. * @throws OutOfBoundsException If the position is invalid
  245. */
  246. #[\ReturnTypeWillChange]
  247. public function seek($position)
  248. {
  249. if (!isset($this->errors[$position])) {
  250. throw new OutOfBoundsException('The offset '.$position.' does not exist.');
  251. }
  252. reset($this->errors);
  253. while ($position !== key($this->errors)) {
  254. next($this->errors);
  255. }
  256. }
  257. /**
  258. * Creates iterator for errors with specific codes.
  259. *
  260. * @param string|string[] $codes The codes to find
  261. *
  262. * @return static
  263. */
  264. public function findByCodes($codes)
  265. {
  266. $codes = (array) $codes;
  267. $errors = [];
  268. foreach ($this as $error) {
  269. $cause = $error->getCause();
  270. if ($cause instanceof ConstraintViolation && \in_array($cause->getCode(), $codes, true)) {
  271. $errors[] = $error;
  272. }
  273. }
  274. return new static($this->form, $errors);
  275. }
  276. /**
  277. * Utility function for indenting multi-line strings.
  278. */
  279. private static function indent(string $string): string
  280. {
  281. return rtrim(self::INDENTATION.str_replace("\n", "\n".self::INDENTATION, $string), ' ');
  282. }
  283. }