206 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			206 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			PHP
		
	
	
|  | <?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\PropertyAccess; | ||
|  | 
 | ||
|  | use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; | ||
|  | use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; | ||
|  | use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Default implementation of {@link PropertyPathInterface}. | ||
|  |  * | ||
|  |  * @author Bernhard Schussek <bschussek@gmail.com> | ||
|  |  */ | ||
|  | class PropertyPath implements \IteratorAggregate, PropertyPathInterface | ||
|  | { | ||
|  |     /** | ||
|  |      * Character used for separating between plural and singular of an element. | ||
|  |      */ | ||
|  |     public const SINGULAR_SEPARATOR = '|'; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The elements of the property path. | ||
|  |      * | ||
|  |      * @var array | ||
|  |      */ | ||
|  |     private $elements = []; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * The number of elements in the property path. | ||
|  |      * | ||
|  |      * @var int | ||
|  |      */ | ||
|  |     private $length; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Contains a Boolean for each property in $elements denoting whether this | ||
|  |      * element is an index. It is a property otherwise. | ||
|  |      * | ||
|  |      * @var array | ||
|  |      */ | ||
|  |     private $isIndex = []; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * String representation of the path. | ||
|  |      * | ||
|  |      * @var string | ||
|  |      */ | ||
|  |     private $pathAsString; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Constructs a property path from a string. | ||
|  |      * | ||
|  |      * @param PropertyPath|string $propertyPath The property path as string or instance | ||
|  |      * | ||
|  |      * @throws InvalidArgumentException     If the given path is not a string | ||
|  |      * @throws InvalidPropertyPathException If the syntax of the property path is not valid | ||
|  |      */ | ||
|  |     public function __construct($propertyPath) | ||
|  |     { | ||
|  |         // Can be used as copy constructor
 | ||
|  |         if ($propertyPath instanceof self) { | ||
|  |             /* @var PropertyPath $propertyPath */ | ||
|  |             $this->elements = $propertyPath->elements; | ||
|  |             $this->length = $propertyPath->length; | ||
|  |             $this->isIndex = $propertyPath->isIndex; | ||
|  |             $this->pathAsString = $propertyPath->pathAsString; | ||
|  | 
 | ||
|  |             return; | ||
|  |         } | ||
|  |         if (!\is_string($propertyPath)) { | ||
|  |             throw new InvalidArgumentException(sprintf('The property path constructor needs a string or an instance of "Symfony\Component\PropertyAccess\PropertyPath". Got: "%s".', \is_object($propertyPath) ? \get_class($propertyPath) : \gettype($propertyPath))); | ||
|  |         } | ||
|  | 
 | ||
|  |         if ('' === $propertyPath) { | ||
|  |             throw new InvalidPropertyPathException('The property path should not be empty.'); | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->pathAsString = $propertyPath; | ||
|  |         $position = 0; | ||
|  |         $remaining = $propertyPath; | ||
|  | 
 | ||
|  |         // first element is evaluated differently - no leading dot for properties
 | ||
|  |         $pattern = '/^(([^\.\[]++)|\[([^\]]++)\])(.*)/'; | ||
|  | 
 | ||
|  |         while (preg_match($pattern, $remaining, $matches)) { | ||
|  |             if ('' !== $matches[2]) { | ||
|  |                 $element = $matches[2]; | ||
|  |                 $this->isIndex[] = false; | ||
|  |             } else { | ||
|  |                 $element = $matches[3]; | ||
|  |                 $this->isIndex[] = true; | ||
|  |             } | ||
|  | 
 | ||
|  |             $this->elements[] = $element; | ||
|  | 
 | ||
|  |             $position += \strlen($matches[1]); | ||
|  |             $remaining = $matches[4]; | ||
|  |             $pattern = '/^(\.([^\.|\[]++)|\[([^\]]++)\])(.*)/'; | ||
|  |         } | ||
|  | 
 | ||
|  |         if ('' !== $remaining) { | ||
|  |             throw new InvalidPropertyPathException(sprintf('Could not parse property path "%s". Unexpected token "%s" at position %d.', $propertyPath, $remaining[0], $position)); | ||
|  |         } | ||
|  | 
 | ||
|  |         $this->length = \count($this->elements); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@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; | ||
|  |         $parent->pathAsString = substr($parent->pathAsString, 0, max(strrpos($parent->pathAsString, '.'), strrpos($parent->pathAsString, '['))); | ||
|  |         array_pop($parent->elements); | ||
|  |         array_pop($parent->isIndex); | ||
|  | 
 | ||
|  |         return $parent; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Returns a new iterator for this path. | ||
|  |      * | ||
|  |      * @return PropertyPathIteratorInterface | ||
|  |      */ | ||
|  |     public function getIterator() | ||
|  |     { | ||
|  |         return new PropertyPathIterator($this); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function getElements() | ||
|  |     { | ||
|  |         return $this->elements; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function getElement($index) | ||
|  |     { | ||
|  |         if (!isset($this->elements[$index])) { | ||
|  |             throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); | ||
|  |         } | ||
|  | 
 | ||
|  |         return $this->elements[$index]; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function isProperty($index) | ||
|  |     { | ||
|  |         if (!isset($this->isIndex[$index])) { | ||
|  |             throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); | ||
|  |         } | ||
|  | 
 | ||
|  |         return !$this->isIndex[$index]; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritdoc} | ||
|  |      */ | ||
|  |     public function isIndex($index) | ||
|  |     { | ||
|  |         if (!isset($this->isIndex[$index])) { | ||
|  |             throw new OutOfBoundsException(sprintf('The index "%s" is not within the property path.', $index)); | ||
|  |         } | ||
|  | 
 | ||
|  |         return $this->isIndex[$index]; | ||
|  |     } | ||
|  | } |