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(), $codestrue)) {
  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. }