diff options
author | Nicolas LÅ“uillet <nicolas.loeuillet@gmail.com> | 2013-08-03 19:26:54 +0200 |
---|---|---|
committer | Nicolas LÅ“uillet <nicolas.loeuillet@gmail.com> | 2013-08-03 19:26:54 +0200 |
commit | 4f5b44bd3bd490309eb2ba7b44df4769816ba729 (patch) | |
tree | 6cefe170dfe0a5a361cb1e2d1fc4d580a3316d02 /vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyAccessor.php | |
parent | 2b840e0cfb63a453bea67a98541f3df9c273c5f5 (diff) | |
download | wallabag-4f5b44bd3bd490309eb2ba7b44df4769816ba729.tar.gz wallabag-4f5b44bd3bd490309eb2ba7b44df4769816ba729.tar.zst wallabag-4f5b44bd3bd490309eb2ba7b44df4769816ba729.zip |
twig implementation
Diffstat (limited to 'vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyAccessor.php')
-rw-r--r-- | vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyAccessor.php | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyAccessor.php b/vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyAccessor.php new file mode 100644 index 00000000..c65d919e --- /dev/null +++ b/vendor/symfony/property-access/Symfony/Component/PropertyAccess/PropertyAccessor.php | |||
@@ -0,0 +1,442 @@ | |||
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\NoSuchPropertyException; | ||
15 | use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException; | ||
16 | |||
17 | /** | ||
18 | * Default implementation of {@link PropertyAccessorInterface}. | ||
19 | * | ||
20 | * @author Bernhard Schussek <bschussek@gmail.com> | ||
21 | */ | ||
22 | class PropertyAccessor implements PropertyAccessorInterface | ||
23 | { | ||
24 | const VALUE = 0; | ||
25 | const IS_REF = 1; | ||
26 | |||
27 | private $magicCall; | ||
28 | |||
29 | /** | ||
30 | * Should not be used by application code. Use | ||
31 | * {@link PropertyAccess::getPropertyAccessor()} instead. | ||
32 | */ | ||
33 | public function __construct($magicCall = false) | ||
34 | { | ||
35 | $this->magicCall = $magicCall; | ||
36 | } | ||
37 | |||
38 | /** | ||
39 | * {@inheritdoc} | ||
40 | */ | ||
41 | public function getValue($objectOrArray, $propertyPath) | ||
42 | { | ||
43 | if (is_string($propertyPath)) { | ||
44 | $propertyPath = new PropertyPath($propertyPath); | ||
45 | } elseif (!$propertyPath instanceof PropertyPathInterface) { | ||
46 | throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPathInterface'); | ||
47 | } | ||
48 | |||
49 | $propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength()); | ||
50 | |||
51 | return $propertyValues[count($propertyValues) - 1][self::VALUE]; | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * {@inheritdoc} | ||
56 | */ | ||
57 | public function setValue(&$objectOrArray, $propertyPath, $value) | ||
58 | { | ||
59 | if (is_string($propertyPath)) { | ||
60 | $propertyPath = new PropertyPath($propertyPath); | ||
61 | } elseif (!$propertyPath instanceof PropertyPathInterface) { | ||
62 | throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPathInterface'); | ||
63 | } | ||
64 | |||
65 | $propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1); | ||
66 | $overwrite = true; | ||
67 | |||
68 | // Add the root object to the list | ||
69 | array_unshift($propertyValues, array( | ||
70 | self::VALUE => &$objectOrArray, | ||
71 | self::IS_REF => true, | ||
72 | )); | ||
73 | |||
74 | for ($i = count($propertyValues) - 1; $i >= 0; --$i) { | ||
75 | $objectOrArray =& $propertyValues[$i][self::VALUE]; | ||
76 | |||
77 | if ($overwrite) { | ||
78 | if (!is_object($objectOrArray) && !is_array($objectOrArray)) { | ||
79 | throw new UnexpectedTypeException($objectOrArray, 'object or array'); | ||
80 | } | ||
81 | |||
82 | $property = $propertyPath->getElement($i); | ||
83 | //$singular = $propertyPath->singulars[$i]; | ||
84 | $singular = null; | ||
85 | |||
86 | if ($propertyPath->isIndex($i)) { | ||
87 | $this->writeIndex($objectOrArray, $property, $value); | ||
88 | } else { | ||
89 | $this->writeProperty($objectOrArray, $property, $singular, $value); | ||
90 | } | ||
91 | } | ||
92 | |||
93 | $value =& $objectOrArray; | ||
94 | $overwrite = !$propertyValues[$i][self::IS_REF]; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Reads the path from an object up to a given path index. | ||
100 | * | ||
101 | * @param object|array $objectOrArray The object or array to read from | ||
102 | * @param PropertyPathInterface $propertyPath The property path to read | ||
103 | * @param integer $lastIndex The index up to which should be read | ||
104 | * | ||
105 | * @return array The values read in the path. | ||
106 | * | ||
107 | * @throws UnexpectedTypeException If a value within the path is neither object nor array. | ||
108 | */ | ||
109 | private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex) | ||
110 | { | ||
111 | $propertyValues = array(); | ||
112 | |||
113 | for ($i = 0; $i < $lastIndex; ++$i) { | ||
114 | if (!is_object($objectOrArray) && !is_array($objectOrArray)) { | ||
115 | throw new UnexpectedTypeException($objectOrArray, 'object or array'); | ||
116 | } | ||
117 | |||
118 | $property = $propertyPath->getElement($i); | ||
119 | $isIndex = $propertyPath->isIndex($i); | ||
120 | $isArrayAccess = is_array($objectOrArray) || $objectOrArray instanceof \ArrayAccess; | ||
121 | |||
122 | // Create missing nested arrays on demand | ||
123 | if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) { | ||
124 | $objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null; | ||
125 | } | ||
126 | |||
127 | if ($isIndex) { | ||
128 | $propertyValue =& $this->readIndex($objectOrArray, $property); | ||
129 | } else { | ||
130 | $propertyValue =& $this->readProperty($objectOrArray, $property); | ||
131 | } | ||
132 | |||
133 | $objectOrArray =& $propertyValue[self::VALUE]; | ||
134 | |||
135 | $propertyValues[] =& $propertyValue; | ||
136 | } | ||
137 | |||
138 | return $propertyValues; | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * Reads a key from an array-like structure. | ||
143 | * | ||
144 | * @param \ArrayAccess|array $array The array or \ArrayAccess object to read from | ||
145 | * @param string|integer $index The key to read | ||
146 | * | ||
147 | * @return mixed The value of the key | ||
148 | * | ||
149 | * @throws NoSuchPropertyException If the array does not implement \ArrayAccess or it is not an array | ||
150 | */ | ||
151 | private function &readIndex(&$array, $index) | ||
152 | { | ||
153 | if (!$array instanceof \ArrayAccess && !is_array($array)) { | ||
154 | throw new NoSuchPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($array))); | ||
155 | } | ||
156 | |||
157 | // Use an array instead of an object since performance is very crucial here | ||
158 | $result = array( | ||
159 | self::VALUE => null, | ||
160 | self::IS_REF => false | ||
161 | ); | ||
162 | |||
163 | if (isset($array[$index])) { | ||
164 | if (is_array($array)) { | ||
165 | $result[self::VALUE] =& $array[$index]; | ||
166 | $result[self::IS_REF] = true; | ||
167 | } else { | ||
168 | $result[self::VALUE] = $array[$index]; | ||
169 | // Objects are always passed around by reference | ||
170 | $result[self::IS_REF] = is_object($array[$index]) ? true : false; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | return $result; | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * Reads the a property from an object or array. | ||
179 | * | ||
180 | * @param object $object The object to read from. | ||
181 | * @param string $property The property to read. | ||
182 | * | ||
183 | * @return mixed The value of the read property | ||
184 | * | ||
185 | * @throws NoSuchPropertyException If the property does not exist or is not | ||
186 | * public. | ||
187 | */ | ||
188 | private function &readProperty(&$object, $property) | ||
189 | { | ||
190 | // Use an array instead of an object since performance is | ||
191 | // very crucial here | ||
192 | $result = array( | ||
193 | self::VALUE => null, | ||
194 | self::IS_REF => false | ||
195 | ); | ||
196 | |||
197 | if (!is_object($object)) { | ||
198 | throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you should write the property path as "[%s]" instead?', $property, $property)); | ||
199 | } | ||
200 | |||
201 | $camelProp = $this->camelize($property); | ||
202 | $reflClass = new \ReflectionClass($object); | ||
203 | $getter = 'get'.$camelProp; | ||
204 | $isser = 'is'.$camelProp; | ||
205 | $hasser = 'has'.$camelProp; | ||
206 | $classHasProperty = $reflClass->hasProperty($property); | ||
207 | |||
208 | if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) { | ||
209 | $result[self::VALUE] = $object->$getter(); | ||
210 | } elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) { | ||
211 | $result[self::VALUE] = $object->$isser(); | ||
212 | } elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) { | ||
213 | $result[self::VALUE] = $object->$hasser(); | ||
214 | } elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) { | ||
215 | $result[self::VALUE] = $object->$property; | ||
216 | } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) { | ||
217 | $result[self::VALUE] =& $object->$property; | ||
218 | $result[self::IS_REF] = true; | ||
219 | } elseif (!$classHasProperty && property_exists($object, $property)) { | ||
220 | // Needed to support \stdClass instances. We need to explicitly | ||
221 | // exclude $classHasProperty, otherwise if in the previous clause | ||
222 | // a *protected* property was found on the class, property_exists() | ||
223 | // returns true, consequently the following line will result in a | ||
224 | // fatal error. | ||
225 | $result[self::VALUE] =& $object->$property; | ||
226 | $result[self::IS_REF] = true; | ||
227 | } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { | ||
228 | // we call the getter and hope the __call do the job | ||
229 | $result[self::VALUE] = $object->$getter(); | ||
230 | } else { | ||
231 | throw new NoSuchPropertyException(sprintf( | ||
232 | 'Neither the property "%s" nor one of the methods "%s()", '. | ||
233 | '"%s()", "%s()", "__get()" or "__call()" exist and have public access in '. | ||
234 | 'class "%s".', | ||
235 | $property, | ||
236 | $getter, | ||
237 | $isser, | ||
238 | $hasser, | ||
239 | $reflClass->name | ||
240 | )); | ||
241 | } | ||
242 | |||
243 | // Objects are always passed around by reference | ||
244 | if (is_object($result[self::VALUE])) { | ||
245 | $result[self::IS_REF] = true; | ||
246 | } | ||
247 | |||
248 | return $result; | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * Sets the value of the property at the given index in the path | ||
253 | * | ||
254 | * @param \ArrayAccess|array $array An array or \ArrayAccess object to write to | ||
255 | * @param string|integer $index The index to write at | ||
256 | * @param mixed $value The value to write | ||
257 | * | ||
258 | * @throws NoSuchPropertyException If the array does not implement \ArrayAccess or it is not an array | ||
259 | */ | ||
260 | private function writeIndex(&$array, $index, $value) | ||
261 | { | ||
262 | if (!$array instanceof \ArrayAccess && !is_array($array)) { | ||
263 | throw new NoSuchPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $index, get_class($array))); | ||
264 | } | ||
265 | |||
266 | $array[$index] = $value; | ||
267 | } | ||
268 | |||
269 | /** | ||
270 | * Sets the value of the property at the given index in the path | ||
271 | * | ||
272 | * @param object|array $object The object or array to write to | ||
273 | * @param string $property The property to write | ||
274 | * @param string|null $singular The singular form of the property name or null | ||
275 | * @param mixed $value The value to write | ||
276 | * | ||
277 | * @throws NoSuchPropertyException If the property does not exist or is not | ||
278 | * public. | ||
279 | */ | ||
280 | private function writeProperty(&$object, $property, $singular, $value) | ||
281 | { | ||
282 | $guessedAdders = ''; | ||
283 | |||
284 | if (!is_object($object)) { | ||
285 | throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property)); | ||
286 | } | ||
287 | |||
288 | $reflClass = new \ReflectionClass($object); | ||
289 | $plural = $this->camelize($property); | ||
290 | |||
291 | // Any of the two methods is required, but not yet known | ||
292 | $singulars = null !== $singular ? array($singular) : (array) StringUtil::singularify($plural); | ||
293 | |||
294 | if (is_array($value) || $value instanceof \Traversable) { | ||
295 | $methods = $this->findAdderAndRemover($reflClass, $singulars); | ||
296 | |||
297 | if (null !== $methods) { | ||
298 | // At this point the add and remove methods have been found | ||
299 | // Use iterator_to_array() instead of clone in order to prevent side effects | ||
300 | // see https://github.com/symfony/symfony/issues/4670 | ||
301 | $itemsToAdd = is_object($value) ? iterator_to_array($value) : $value; | ||
302 | $itemToRemove = array(); | ||
303 | $propertyValue = $this->readProperty($object, $property); | ||
304 | $previousValue = $propertyValue[self::VALUE]; | ||
305 | |||
306 | if (is_array($previousValue) || $previousValue instanceof \Traversable) { | ||
307 | foreach ($previousValue as $previousItem) { | ||
308 | foreach ($value as $key => $item) { | ||
309 | if ($item === $previousItem) { | ||
310 | // Item found, don't add | ||
311 | unset($itemsToAdd[$key]); | ||
312 | |||
313 | // Next $previousItem | ||
314 | continue 2; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | // Item not found, add to remove list | ||
319 | $itemToRemove[] = $previousItem; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | foreach ($itemToRemove as $item) { | ||
324 | call_user_func(array($object, $methods[1]), $item); | ||
325 | } | ||
326 | |||
327 | foreach ($itemsToAdd as $item) { | ||
328 | call_user_func(array($object, $methods[0]), $item); | ||
329 | } | ||
330 | |||
331 | return; | ||
332 | } else { | ||
333 | // It is sufficient to include only the adders in the error | ||
334 | // message. If the user implements the adder but not the remover, | ||
335 | // an exception will be thrown in findAdderAndRemover() that | ||
336 | // the remover has to be implemented as well. | ||
337 | $guessedAdders = '"add'.implode('()", "add', $singulars).'()", '; | ||
338 | } | ||
339 | } | ||
340 | |||
341 | $setter = 'set'.$this->camelize($property); | ||
342 | $classHasProperty = $reflClass->hasProperty($property); | ||
343 | |||
344 | if ($reflClass->hasMethod($setter) && $reflClass->getMethod($setter)->isPublic()) { | ||
345 | $object->$setter($value); | ||
346 | } elseif ($reflClass->hasMethod('__set') && $reflClass->getMethod('__set')->isPublic()) { | ||
347 | $object->$property = $value; | ||
348 | } elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) { | ||
349 | $object->$property = $value; | ||
350 | } elseif (!$classHasProperty && property_exists($object, $property)) { | ||
351 | // Needed to support \stdClass instances. We need to explicitly | ||
352 | // exclude $classHasProperty, otherwise if in the previous clause | ||
353 | // a *protected* property was found on the class, property_exists() | ||
354 | // returns true, consequently the following line will result in a | ||
355 | // fatal error. | ||
356 | $object->$property = $value; | ||
357 | } elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) { | ||
358 | // we call the getter and hope the __call do the job | ||
359 | $object->$setter($value); | ||
360 | } else { | ||
361 | throw new NoSuchPropertyException(sprintf( | ||
362 | 'Neither the property "%s" nor one of the methods %s"%s()", '. | ||
363 | '"__set()" or "__call()" exist and have public access in class "%s".', | ||
364 | $property, | ||
365 | $guessedAdders, | ||
366 | $setter, | ||
367 | $reflClass->name | ||
368 | )); | ||
369 | } | ||
370 | } | ||
371 | |||
372 | /** | ||
373 | * Camelizes a given string. | ||
374 | * | ||
375 | * @param string $string Some string | ||
376 | * | ||
377 | * @return string The camelized version of the string | ||
378 | */ | ||
379 | private function camelize($string) | ||
380 | { | ||
381 | return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string); | ||
382 | } | ||
383 | |||
384 | /** | ||
385 | * Searches for add and remove methods. | ||
386 | * | ||
387 | * @param \ReflectionClass $reflClass The reflection class for the given object | ||
388 | * @param array $singulars The singular form of the property name or null | ||
389 | * | ||
390 | * @return array|null An array containing the adder and remover when found, null otherwise | ||
391 | * | ||
392 | * @throws NoSuchPropertyException If the property does not exist | ||
393 | */ | ||
394 | private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars) | ||
395 | { | ||
396 | foreach ($singulars as $singular) { | ||
397 | $addMethod = 'add'.$singular; | ||
398 | $removeMethod = 'remove'.$singular; | ||
399 | |||
400 | $addMethodFound = $this->isAccessible($reflClass, $addMethod, 1); | ||
401 | $removeMethodFound = $this->isAccessible($reflClass, $removeMethod, 1); | ||
402 | |||
403 | if ($addMethodFound && $removeMethodFound) { | ||
404 | return array($addMethod, $removeMethod); | ||
405 | } | ||
406 | |||
407 | if ($addMethodFound xor $removeMethodFound) { | ||
408 | throw new NoSuchPropertyException(sprintf( | ||
409 | 'Found the public method "%s()", but did not find a public "%s()" on class %s', | ||
410 | $addMethodFound ? $addMethod : $removeMethod, | ||
411 | $addMethodFound ? $removeMethod : $addMethod, | ||
412 | $reflClass->name | ||
413 | )); | ||
414 | } | ||
415 | } | ||
416 | |||
417 | return null; | ||
418 | } | ||
419 | |||
420 | /** | ||
421 | * Returns whether a method is public and has a specific number of required parameters. | ||
422 | * | ||
423 | * @param \ReflectionClass $class The class of the method | ||
424 | * @param string $methodName The method name | ||
425 | * @param integer $parameters The number of parameters | ||
426 | * | ||
427 | * @return Boolean Whether the method is public and has $parameters | ||
428 | * required parameters | ||
429 | */ | ||
430 | private function isAccessible(\ReflectionClass $class, $methodName, $parameters) | ||
431 | { | ||
432 | if ($class->hasMethod($methodName)) { | ||
433 | $method = $class->getMethod($methodName); | ||
434 | |||
435 | if ($method->isPublic() && $method->getNumberOfRequiredParameters() === $parameters) { | ||
436 | return true; | ||
437 | } | ||
438 | } | ||
439 | |||
440 | return false; | ||
441 | } | ||
442 | } | ||