vendor/symfony/form/FormErrorIterator.php line 36

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