vendor/symfony/form/FormErrorIterator.php line 38

  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 FormInterface $form;
  41.     /**
  42.      * @var list<T>
  43.      */
  44.     private array $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.     public function __toString(): string
  64.     {
  65.         $string '';
  66.         foreach ($this->errors as $error) {
  67.             if ($error instanceof FormError) {
  68.                 $string .= 'ERROR: '.$error->getMessage()."\n";
  69.             } else {
  70.                 /* @var self $error */
  71.                 $string .= $error->getForm()->getName().":\n";
  72.                 $string .= self::indent((string) $error);
  73.             }
  74.         }
  75.         return $string;
  76.     }
  77.     /**
  78.      * Returns the iterated form.
  79.      */
  80.     public function getForm(): FormInterface
  81.     {
  82.         return $this->form;
  83.     }
  84.     /**
  85.      * Returns the current element of the iterator.
  86.      *
  87.      * @return T An error or an iterator containing nested errors
  88.      */
  89.     public function current(): FormError|self
  90.     {
  91.         return current($this->errors);
  92.     }
  93.     /**
  94.      * Advances the iterator to the next position.
  95.      */
  96.     public function next(): void
  97.     {
  98.         next($this->errors);
  99.     }
  100.     /**
  101.      * Returns the current position of the iterator.
  102.      */
  103.     public function key(): int
  104.     {
  105.         return key($this->errors);
  106.     }
  107.     /**
  108.      * Returns whether the iterator's position is valid.
  109.      */
  110.     public function valid(): bool
  111.     {
  112.         return null !== key($this->errors);
  113.     }
  114.     /**
  115.      * Sets the iterator's position to the beginning.
  116.      *
  117.      * This method detects if errors have been added to the form since the
  118.      * construction of the iterator.
  119.      */
  120.     public function rewind(): void
  121.     {
  122.         reset($this->errors);
  123.     }
  124.     /**
  125.      * Returns whether a position exists in the iterator.
  126.      *
  127.      * @param int $position The position
  128.      */
  129.     public function offsetExists(mixed $position): bool
  130.     {
  131.         return isset($this->errors[$position]);
  132.     }
  133.     /**
  134.      * Returns the element at a position in the iterator.
  135.      *
  136.      * @param int $position The position
  137.      *
  138.      * @return T
  139.      *
  140.      * @throws OutOfBoundsException If the given position does not exist
  141.      */
  142.     public function offsetGet(mixed $position): FormError|self
  143.     {
  144.         if (!isset($this->errors[$position])) {
  145.             throw new OutOfBoundsException('The offset '.$position.' does not exist.');
  146.         }
  147.         return $this->errors[$position];
  148.     }
  149.     /**
  150.      * Unsupported method.
  151.      *
  152.      * @throws BadMethodCallException
  153.      */
  154.     public function offsetSet(mixed $positionmixed $value): void
  155.     {
  156.         throw new BadMethodCallException('The iterator doesn\'t support modification of elements.');
  157.     }
  158.     /**
  159.      * Unsupported method.
  160.      *
  161.      * @throws BadMethodCallException
  162.      */
  163.     public function offsetUnset(mixed $position): void
  164.     {
  165.         throw new BadMethodCallException('The iterator doesn\'t support modification of elements.');
  166.     }
  167.     /**
  168.      * Returns whether the current element of the iterator can be recursed
  169.      * into.
  170.      */
  171.     public function hasChildren(): bool
  172.     {
  173.         return current($this->errors) instanceof self;
  174.     }
  175.     public function getChildren(): self
  176.     {
  177.         if (!$this->hasChildren()) {
  178.             throw new LogicException(sprintf('The current element is not iterable. Use "%s" to get the current element.'self::class.'::current()'));
  179.         }
  180.         /** @var self $children */
  181.         $children current($this->errors);
  182.         return $children;
  183.     }
  184.     /**
  185.      * Returns the number of elements in the iterator.
  186.      *
  187.      * Note that this is not the total number of errors, if the constructor
  188.      * parameter $deep was set to true! In that case, you should wrap the
  189.      * iterator into a {@link \RecursiveIteratorIterator} with the standard mode
  190.      * {@link \RecursiveIteratorIterator::LEAVES_ONLY} and count the result.
  191.      *
  192.      *     $iterator = new \RecursiveIteratorIterator($form->getErrors(true));
  193.      *     $count = count(iterator_to_array($iterator));
  194.      *
  195.      * Alternatively, set the constructor argument $flatten to true as well.
  196.      *
  197.      *     $count = count($form->getErrors(true, true));
  198.      */
  199.     public function count(): int
  200.     {
  201.         return \count($this->errors);
  202.     }
  203.     /**
  204.      * Sets the position of the iterator.
  205.      *
  206.      * @throws OutOfBoundsException If the position is invalid
  207.      */
  208.     public function seek(int $position): void
  209.     {
  210.         if (!isset($this->errors[$position])) {
  211.             throw new OutOfBoundsException('The offset '.$position.' does not exist.');
  212.         }
  213.         reset($this->errors);
  214.         while ($position !== key($this->errors)) {
  215.             next($this->errors);
  216.         }
  217.     }
  218.     /**
  219.      * Creates iterator for errors with specific codes.
  220.      *
  221.      * @param string|string[] $codes The codes to find
  222.      */
  223.     public function findByCodes(string|array $codes): static
  224.     {
  225.         $codes = (array) $codes;
  226.         $errors = [];
  227.         foreach ($this as $error) {
  228.             $cause $error->getCause();
  229.             if ($cause instanceof ConstraintViolation && \in_array($cause->getCode(), $codestrue)) {
  230.                 $errors[] = $error;
  231.             }
  232.         }
  233.         return new static($this->form$errors);
  234.     }
  235.     /**
  236.      * Utility function for indenting multi-line strings.
  237.      */
  238.     private static function indent(string $string): string
  239.     {
  240.         return rtrim(self::INDENTATION.str_replace("\n""\n".self::INDENTATION$string), ' ');
  241.     }
  242. }