PropertyPath.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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\PropertyAccess;
  11. use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException;
  12. use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException;
  13. use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
  14. /**
  15. * Default implementation of {@link PropertyPathInterface}.
  16. *
  17. * @author Bernhard Schussek <bschussek@gmail.com>
  18. */
  19. class PropertyPath implements \IteratorAggregate, PropertyPathInterface
  20. {
  21. /**
  22. * Character used for separating between plural and singular of an element.
  23. * @var string
  24. */
  25. const SINGULAR_SEPARATOR = '|';
  26. /**
  27. * The elements of the property path
  28. * @var array
  29. */
  30. private $elements = array();
  31. /**
  32. * The singular forms of the elements in the property path.
  33. * @var array
  34. */
  35. private $singulars = array();
  36. /**
  37. * The number of elements in the property path
  38. * @var integer
  39. */
  40. private $length;
  41. /**
  42. * Contains a Boolean for each property in $elements denoting whether this
  43. * element is an index. It is a property otherwise.
  44. * @var array
  45. */
  46. private $isIndex = array();
  47. /**
  48. * String representation of the path
  49. * @var string
  50. */
  51. private $pathAsString;
  52. /**
  53. * Constructs a property path from a string.
  54. *
  55. * @param PropertyPath|string $propertyPath The property path as string or instance
  56. *
  57. * @throws UnexpectedTypeException If the given path is not a string
  58. * @throws InvalidPropertyPathException If the syntax of the property path is not valid
  59. */
  60. public function __construct($propertyPath)
  61. {
  62. // Can be used as copy constructor
  63. if ($propertyPath instanceof PropertyPath) {
  64. /* @var PropertyPath $propertyPath */
  65. $this->elements = $propertyPath->elements;
  66. $this->singulars = $propertyPath->singulars;
  67. $this->length = $propertyPath->length;
  68. $this->isIndex = $propertyPath->isIndex;
  69. $this->pathAsString = $propertyPath->pathAsString;
  70. return;
  71. }
  72. if (!is_string($propertyPath)) {
  73. throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPath');
  74. }
  75. if ('' === $propertyPath) {
  76. throw new InvalidPropertyPathException('The property path should not be empty.');
  77. }
  78. $this->pathAsString = $propertyPath;
  79. $position = 0;
  80. $remaining = $propertyPath;
  81. // first element is evaluated differently - no leading dot for properties
  82. $pattern = '/^(([^\.\[]+)|\[([^\]]+)\])(.*)/';
  83. while (preg_match($pattern, $remaining, $matches)) {
  84. if ('' !== $matches[2]) {
  85. $element = $matches[2];
  86. $this->isIndex[] = false;
  87. } else {
  88. $element = $matches[3];
  89. $this->isIndex[] = true;
  90. }
  91. // Disabled this behaviour as the syntax is not yet final
  92. //$pos = strpos($element, self::SINGULAR_SEPARATOR);
  93. $pos = false;
  94. $singular = null;
  95. if (false !== $pos) {
  96. $singular = substr($element, $pos + 1);
  97. $element = substr($element, 0, $pos);
  98. }
  99. $this->elements[] = $element;
  100. $this->singulars[] = $singular;
  101. $position += strlen($matches[1]);
  102. $remaining = $matches[4];
  103. $pattern = '/^(\.(\w+)|\[([^\]]+)\])(.*)/';
  104. }
  105. if ('' !== $remaining) {
  106. throw new InvalidPropertyPathException(sprintf(
  107. 'Could not parse property path "%s". Unexpected token "%s" at position %d',
  108. $propertyPath,
  109. $remaining{0},
  110. $position
  111. ));
  112. }
  113. $this->length = count($this->elements);
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function __toString()
  119. {
  120. return $this->pathAsString;
  121. }
  122. /**
  123. * {@inheritdoc}
  124. */
  125. public function getLength()
  126. {
  127. return $this->length;
  128. }
  129. /**
  130. * {@inheritdoc}
  131. */
  132. public function getParent()
  133. {
  134. if ($this->length <= 1) {
  135. return null;
  136. }
  137. $parent = clone $this;
  138. --$parent->length;
  139. $parent->pathAsString = substr($parent->pathAsString, 0, max(strrpos($parent->pathAsString, '.'), strrpos($parent->pathAsString, '[')));
  140. array_pop($parent->elements);
  141. array_pop($parent->singulars);
  142. array_pop($parent->isIndex);
  143. return $parent;
  144. }
  145. /**
  146. * Returns a new iterator for this path
  147. *
  148. * @return PropertyPathIteratorInterface
  149. */
  150. public function getIterator()
  151. {
  152. return new PropertyPathIterator($this);
  153. }
  154. /**
  155. * {@inheritdoc}
  156. */
  157. public function getElements()
  158. {
  159. return $this->elements;
  160. }
  161. /**
  162. * {@inheritdoc}
  163. */
  164. public function getElement($index)
  165. {
  166. if (!isset($this->elements[$index])) {
  167. throw new OutOfBoundsException(sprintf('The index %s is not within the property path', $index));
  168. }
  169. return $this->elements[$index];
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function isProperty($index)
  175. {
  176. if (!isset($this->isIndex[$index])) {
  177. throw new OutOfBoundsException(sprintf('The index %s is not within the property path', $index));
  178. }
  179. return !$this->isIndex[$index];
  180. }
  181. /**
  182. * {@inheritdoc}
  183. */
  184. public function isIndex($index)
  185. {
  186. if (!isset($this->isIndex[$index])) {
  187. throw new OutOfBoundsException(sprintf('The index %s is not within the property path', $index));
  188. }
  189. return $this->isIndex[$index];
  190. }
  191. }