123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
- use Symfony\Component\Form\Exception\OutOfBoundsException;
- use Symfony\Component\PropertyAccess\PropertyPath;
- use Symfony\Component\PropertyAccess\PropertyPathInterface;
- /**
- * @author Bernhard Schussek <bschussek@gmail.com>
- */
- class ViolationPath implements \IteratorAggregate, PropertyPathInterface
- {
- /**
- * @var array
- */
- private $elements = [];
- /**
- * @var array
- */
- private $isIndex = [];
- /**
- * @var array
- */
- private $mapsForm = [];
- /**
- * @var string
- */
- private $pathAsString = '';
- /**
- * @var int
- */
- private $length = 0;
- /**
- * Creates a new violation path from a string.
- *
- * @param string $violationPath The property path of a {@link \Symfony\Component\Validator\ConstraintViolation} object
- */
- public function __construct(string $violationPath)
- {
- $path = new PropertyPath($violationPath);
- $elements = $path->getElements();
- $data = false;
- for ($i = 0, $l = \count($elements); $i < $l; ++$i) {
- if (!$data) {
- // The element "data" has not yet been passed
- if ('children' === $elements[$i] && $path->isProperty($i)) {
- // Skip element "children"
- ++$i;
- // Next element must exist and must be an index
- // Otherwise consider this the end of the path
- if ($i >= $l || !$path->isIndex($i)) {
- break;
- }
- // All the following index items (regardless if .children is
- // explicitly used) are children and grand-children
- for (; $i < $l && $path->isIndex($i); ++$i) {
- $this->elements[] = $elements[$i];
- $this->isIndex[] = true;
- $this->mapsForm[] = true;
- }
- // Rewind the pointer as the last element above didn't match
- // (even if the pointer was moved forward)
- --$i;
- } elseif ('data' === $elements[$i] && $path->isProperty($i)) {
- // Skip element "data"
- ++$i;
- // End of path
- if ($i >= $l) {
- break;
- }
- $this->elements[] = $elements[$i];
- $this->isIndex[] = $path->isIndex($i);
- $this->mapsForm[] = false;
- $data = true;
- } else {
- // Neither "children" nor "data" property found
- // Consider this the end of the path
- break;
- }
- } else {
- // Already after the "data" element
- // Pick everything as is
- $this->elements[] = $elements[$i];
- $this->isIndex[] = $path->isIndex($i);
- $this->mapsForm[] = false;
- }
- }
- $this->length = \count($this->elements);
- $this->buildString();
- }
- /**
- * {@inheritdoc}
- */
- public function __toString()
- {
- return $this->pathAsString;
- }
- /**
- * {@inheritdoc}
- */
- public function getLength()
- {
- return $this->length;
- }
- /**
- * {@inheritdoc}
- */
- public function getParent()
- {
- if ($this->length <= 1) {
- return null;
- }
- $parent = clone $this;
- --$parent->length;
- array_pop($parent->elements);
- array_pop($parent->isIndex);
- array_pop($parent->mapsForm);
- $parent->buildString();
- return $parent;
- }
- /**
- * {@inheritdoc}
- */
- public function getElements()
- {
- return $this->elements;
- }
- /**
- * {@inheritdoc}
- */
- public function getElement(int $index)
- {
- if (!isset($this->elements[$index])) {
- throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
- }
- return $this->elements[$index];
- }
- /**
- * {@inheritdoc}
- */
- public function isProperty(int $index)
- {
- if (!isset($this->isIndex[$index])) {
- throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
- }
- return !$this->isIndex[$index];
- }
- /**
- * {@inheritdoc}
- */
- public function isIndex(int $index)
- {
- if (!isset($this->isIndex[$index])) {
- throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
- }
- return $this->isIndex[$index];
- }
- /**
- * Returns whether an element maps directly to a form.
- *
- * Consider the following violation path:
- *
- * children[address].children[office].data.street
- *
- * In this example, "address" and "office" map to forms, while
- * "street does not.
- *
- * @return bool Whether the element maps to a form
- *
- * @throws OutOfBoundsException if the offset is invalid
- */
- public function mapsForm(int $index)
- {
- if (!isset($this->mapsForm[$index])) {
- throw new OutOfBoundsException(sprintf('The index "%s" is not within the violation path.', $index));
- }
- return $this->mapsForm[$index];
- }
- /**
- * Returns a new iterator for this path.
- *
- * @return ViolationPathIterator
- */
- public function getIterator()
- {
- return new ViolationPathIterator($this);
- }
- /**
- * Builds the string representation from the elements.
- */
- private function buildString()
- {
- $this->pathAsString = '';
- $data = false;
- foreach ($this->elements as $index => $element) {
- if ($this->mapsForm[$index]) {
- $this->pathAsString .= ".children[$element]";
- } elseif (!$data) {
- $this->pathAsString .= '.data'.($this->isIndex[$index] ? "[$element]" : ".$element");
- $data = true;
- } else {
- $this->pathAsString .= $this->isIndex[$index] ? "[$element]" : ".$element";
- }
- }
- if ('' !== $this->pathAsString) {
- // remove leading dot
- $this->pathAsString = substr($this->pathAsString, 1);
- }
- }
- }
|