FormErrorIterator.php 7.1 KB

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