]> git.immae.eu Git - github/wallabag/wallabag.git/blob - vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyPathBuilder.php
twig implementation
[github/wallabag/wallabag.git] / vendor / symfony / property-access / Symfony / Component / PropertyAccess / PropertyPathBuilder.php
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\PropertyAccess;
13
14 use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException;
15
16 /**
17 * @author Bernhard Schussek <bschussek@gmail.com>
18 */
19 class PropertyPathBuilder
20 {
21 /**
22 * @var array
23 */
24 private $elements = array();
25
26 /**
27 * @var array
28 */
29 private $isIndex = array();
30
31 /**
32 * Creates a new property path builder.
33 *
34 * @param null|PropertyPathInterface|string $path The path to initially store
35 * in the builder. Optional.
36 */
37 public function __construct($path = null)
38 {
39 if (null !== $path) {
40 $this->append($path);
41 }
42 }
43
44 /**
45 * Appends a (sub-) path to the current path.
46 *
47 * @param PropertyPathInterface|string $path The path to append.
48 * @param integer $offset The offset where the appended
49 * piece starts in $path.
50 * @param integer $length The length of the appended piece.
51 * If 0, the full path is appended.
52 */
53 public function append($path, $offset = 0, $length = 0)
54 {
55 if (is_string($path)) {
56 $path = new PropertyPath($path);
57 }
58
59 if (0 === $length) {
60 $end = $path->getLength();
61 } else {
62 $end = $offset + $length;
63 }
64
65 for (; $offset < $end; ++$offset) {
66 $this->elements[] = $path->getElement($offset);
67 $this->isIndex[] = $path->isIndex($offset);
68 }
69 }
70
71 /**
72 * Appends an index element to the current path.
73 *
74 * @param string $name The name of the appended index
75 */
76 public function appendIndex($name)
77 {
78 $this->elements[] = $name;
79 $this->isIndex[] = true;
80 }
81
82 /**
83 * Appends a property element to the current path.
84 *
85 * @param string $name The name of the appended property
86 */
87 public function appendProperty($name)
88 {
89 $this->elements[] = $name;
90 $this->isIndex[] = false;
91 }
92
93 /**
94 * Removes elements from the current path.
95 *
96 * @param integer $offset The offset at which to remove
97 * @param integer $length The length of the removed piece
98 *
99 * @throws OutOfBoundsException if offset is invalid
100 */
101 public function remove($offset, $length = 1)
102 {
103 if (!isset($this->elements[$offset])) {
104 throw new OutOfBoundsException(sprintf('The offset %s is not within the property path', $offset));
105 }
106
107 $this->resize($offset, $length, 0);
108 }
109
110 /**
111 * Replaces a sub-path by a different (sub-) path.
112 *
113 * @param integer $offset The offset at which to replace.
114 * @param integer $length The length of the piece to replace.
115 * @param PropertyPathInterface|string $path The path to insert.
116 * @param integer $pathOffset The offset where the inserted piece
117 * starts in $path.
118 * @param integer $pathLength The length of the inserted piece.
119 * If 0, the full path is inserted.
120 *
121 * @throws OutOfBoundsException If the offset is invalid
122 */
123 public function replace($offset, $length, $path, $pathOffset = 0, $pathLength = 0)
124 {
125 if (is_string($path)) {
126 $path = new PropertyPath($path);
127 }
128
129 if ($offset < 0 && abs($offset) <= $this->getLength()) {
130 $offset = $this->getLength() + $offset;
131 } elseif (!isset($this->elements[$offset])) {
132 throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
133 }
134
135 if (0 === $pathLength) {
136 $pathLength = $path->getLength() - $pathOffset;
137 }
138
139 $this->resize($offset, $length, $pathLength);
140
141 for ($i = 0; $i < $pathLength; ++$i) {
142 $this->elements[$offset + $i] = $path->getElement($pathOffset + $i);
143 $this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i);
144 }
145 }
146
147 /**
148 * Replaces a property element by an index element.
149 *
150 * @param integer $offset The offset at which to replace
151 * @param string $name The new name of the element. Optional.
152 *
153 * @throws OutOfBoundsException If the offset is invalid
154 */
155 public function replaceByIndex($offset, $name = null)
156 {
157 if (!isset($this->elements[$offset])) {
158 throw new OutOfBoundsException(sprintf('The offset %s is not within the property path', $offset));
159 }
160
161 if (null !== $name) {
162 $this->elements[$offset] = $name;
163 }
164
165 $this->isIndex[$offset] = true;
166 }
167
168 /**
169 * Replaces an index element by a property element.
170 *
171 * @param integer $offset The offset at which to replace
172 * @param string $name The new name of the element. Optional.
173 *
174 * @throws OutOfBoundsException If the offset is invalid
175 */
176 public function replaceByProperty($offset, $name = null)
177 {
178 if (!isset($this->elements[$offset])) {
179 throw new OutOfBoundsException(sprintf('The offset %s is not within the property path', $offset));
180 }
181
182 if (null !== $name) {
183 $this->elements[$offset] = $name;
184 }
185
186 $this->isIndex[$offset] = false;
187 }
188
189 /**
190 * Returns the length of the current path.
191 *
192 * @return integer The path length
193 */
194 public function getLength()
195 {
196 return count($this->elements);
197 }
198
199 /**
200 * Returns the current property path.
201 *
202 * @return PropertyPathInterface The constructed property path
203 */
204 public function getPropertyPath()
205 {
206 $pathAsString = $this->__toString();
207
208 return '' !== $pathAsString ? new PropertyPath($pathAsString) : null;
209 }
210
211 /**
212 * Returns the current property path as string.
213 *
214 * @return string The property path as string
215 */
216 public function __toString()
217 {
218 $string = '';
219
220 foreach ($this->elements as $offset => $element) {
221 if ($this->isIndex[$offset]) {
222 $element = '['.$element.']';
223 } elseif ('' !== $string) {
224 $string .= '.';
225 }
226
227 $string .= $element;
228 }
229
230 return $string;
231 }
232
233 /**
234 * Resizes the path so that a chunk of length $cutLength is
235 * removed at $offset and another chunk of length $insertionLength
236 * can be inserted.
237 *
238 * @param integer $offset The offset where the removed chunk starts
239 * @param integer $cutLength The length of the removed chunk
240 * @param integer $insertionLength The length of the inserted chunk
241 */
242 private function resize($offset, $cutLength, $insertionLength)
243 {
244 // Nothing else to do in this case
245 if ($insertionLength === $cutLength) {
246 return;
247 }
248
249 $length = count($this->elements);
250
251 if ($cutLength > $insertionLength) {
252 // More elements should be removed than inserted
253 $diff = $cutLength - $insertionLength;
254 $newLength = $length - $diff;
255
256 // Shift elements to the left (left-to-right until the new end)
257 // Max allowed offset to be shifted is such that
258 // $offset + $diff < $length (otherwise invalid index access)
259 // i.e. $offset < $length - $diff = $newLength
260 for ($i = $offset; $i < $newLength; ++$i) {
261 $this->elements[$i] = $this->elements[$i + $diff];
262 $this->isIndex[$i] = $this->isIndex[$i + $diff];
263 }
264
265 // All remaining elements should be removed
266 for (; $i < $length; ++$i) {
267 unset($this->elements[$i]);
268 unset($this->isIndex[$i]);
269 }
270 } else {
271 $diff = $insertionLength - $cutLength;
272
273 $newLength = $length + $diff;
274 $indexAfterInsertion = $offset + $insertionLength;
275
276 // $diff <= $insertionLength
277 // $indexAfterInsertion >= $insertionLength
278 // => $diff <= $indexAfterInsertion
279
280 // In each of the following loops, $i >= $diff must hold,
281 // otherwise ($i - $diff) becomes negative.
282
283 // Shift old elements to the right to make up space for the
284 // inserted elements. This needs to be done left-to-right in
285 // order to preserve an ascending array index order
286 // Since $i = max($length, $indexAfterInsertion) and $indexAfterInsertion >= $diff,
287 // $i >= $diff is guaranteed.
288 for ($i = max($length, $indexAfterInsertion); $i < $newLength; ++$i) {
289 $this->elements[$i] = $this->elements[$i - $diff];
290 $this->isIndex[$i] = $this->isIndex[$i - $diff];
291 }
292
293 // Shift remaining elements to the right. Do this right-to-left
294 // so we don't overwrite elements before copying them
295 // The last written index is the immediate index after the inserted
296 // string, because the indices before that will be overwritten
297 // anyway.
298 // Since $i >= $indexAfterInsertion and $indexAfterInsertion >= $diff,
299 // $i >= $diff is guaranteed.
300 for ($i = $length - 1; $i >= $indexAfterInsertion; --$i) {
301 $this->elements[$i] = $this->elements[$i - $diff];
302 $this->isIndex[$i] = $this->isIndex[$i - $diff];
303 }
304 }
305 }
306 }