PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.8.2
Kubio AI Page Builder v2.8.2
2.8.4 2.8.3 2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / vendor / symfony / property-access / PropertyAccessor.php
kubio / vendor / symfony / property-access Last commit date
Exception 1 year ago CHANGELOG.md 1 year ago LICENSE 1 year ago PropertyAccess.php 4 years ago PropertyAccessor.php 1 year ago PropertyAccessorBuilder.php 1 year ago PropertyAccessorInterface.php 1 year ago PropertyPath.php 1 year ago PropertyPathBuilder.php 1 year ago PropertyPathInterface.php 1 year ago PropertyPathIterator.php 1 year ago PropertyPathIteratorInterface.php 1 year ago README.md 4 years ago composer.json 1 year ago
PropertyAccessor.php
763 lines
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 Psr\Cache\CacheItemPoolInterface;
15 use Psr\Log\LoggerInterface;
16 use Psr\Log\NullLogger;
17 use Symfony\Component\Cache\Adapter\AdapterInterface;
18 use Symfony\Component\Cache\Adapter\ApcuAdapter;
19 use Symfony\Component\Cache\Adapter\NullAdapter;
20 use Symfony\Component\PropertyAccess\Exception\AccessException;
21 use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
22 use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
23 use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
24 use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
25 use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
26 use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
27 use Symfony\Component\PropertyInfo\PropertyReadInfo;
28 use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
29 use Symfony\Component\PropertyInfo\PropertyWriteInfo;
30 use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
31
32 /**
33 * Default implementation of {@link PropertyAccessorInterface}.
34 *
35 * @author Bernhard Schussek <bschussek@gmail.com>
36 * @author Kévin Dunglas <dunglas@gmail.com>
37 * @author Nicolas Grekas <p@tchwork.com>
38 */
39 class PropertyAccessor implements PropertyAccessorInterface
40 {
41 /** @var int Allow none of the magic methods */
42 public const DISALLOW_MAGIC_METHODS = ReflectionExtractor::DISALLOW_MAGIC_METHODS;
43 /** @var int Allow magic __get methods */
44 public const MAGIC_GET = ReflectionExtractor::ALLOW_MAGIC_GET;
45 /** @var int Allow magic __set methods */
46 public const MAGIC_SET = ReflectionExtractor::ALLOW_MAGIC_SET;
47 /** @var int Allow magic __call methods */
48 public const MAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL;
49
50 public const DO_NOT_THROW = 0;
51 public const THROW_ON_INVALID_INDEX = 1;
52 public const THROW_ON_INVALID_PROPERTY_PATH = 2;
53
54 private const VALUE = 0;
55 private const REF = 1;
56 private const IS_REF_CHAINED = 2;
57 private const CACHE_PREFIX_READ = 'r';
58 private const CACHE_PREFIX_WRITE = 'w';
59 private const CACHE_PREFIX_PROPERTY_PATH = 'p';
60
61 private $magicMethodsFlags;
62 private $ignoreInvalidIndices;
63 private $ignoreInvalidProperty;
64
65 /**
66 * @var CacheItemPoolInterface
67 */
68 private $cacheItemPool;
69
70 private $propertyPathCache = [];
71
72 /**
73 * @var PropertyReadInfoExtractorInterface
74 */
75 private $readInfoExtractor;
76
77 /**
78 * @var PropertyWriteInfoExtractorInterface
79 */
80 private $writeInfoExtractor;
81 private $readPropertyCache = [];
82 private $writePropertyCache = [];
83 private const RESULT_PROTO = [self::VALUE => null];
84
85 /**
86 * Should not be used by application code. Use
87 * {@link PropertyAccess::createPropertyAccessor()} instead.
88 *
89 * @param int $magicMethods A bitwise combination of the MAGIC_* constants
90 * to specify the allowed magic methods (__get, __set, __call)
91 * or self::DISALLOW_MAGIC_METHODS for none
92 * @param int $throw A bitwise combination of the THROW_* constants
93 * to specify when exceptions should be thrown
94 * @param PropertyReadInfoExtractorInterface $readInfoExtractor
95 * @param PropertyWriteInfoExtractorInterface $writeInfoExtractor
96 */
97 public function __construct($magicMethods = self::MAGIC_GET | self::MAGIC_SET, $throw = self::THROW_ON_INVALID_PROPERTY_PATH, ?CacheItemPoolInterface $cacheItemPool = null, $readInfoExtractor = null, $writeInfoExtractor = null)
98 {
99 if (\is_bool($magicMethods)) {
100 trigger_deprecation('symfony/property-access', '5.2', 'Passing a boolean as the first argument to "%s()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).', __METHOD__);
101
102 $magicMethods = ($magicMethods ? self::MAGIC_CALL : 0) | self::MAGIC_GET | self::MAGIC_SET;
103 } elseif (!\is_int($magicMethods)) {
104 throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an integer, "%s" given.', __METHOD__, get_debug_type($readInfoExtractor)));
105 }
106
107 if (\is_bool($throw)) {
108 trigger_deprecation('symfony/property-access', '5.3', 'Passing a boolean as the second argument to "%s()" is deprecated. Pass a combination of bitwise flags instead (i.e an integer).', __METHOD__);
109
110 $throw = $throw ? self::THROW_ON_INVALID_INDEX : self::DO_NOT_THROW;
111
112 if (!\is_bool($readInfoExtractor)) {
113 $throw |= self::THROW_ON_INVALID_PROPERTY_PATH;
114 }
115 }
116
117 if (\is_bool($readInfoExtractor)) {
118 trigger_deprecation('symfony/property-access', '5.3', 'Passing a boolean as the fourth argument to "%s()" is deprecated. Pass a combination of bitwise flags as the second argument instead (i.e an integer).', __METHOD__);
119
120 if ($readInfoExtractor) {
121 $throw |= self::THROW_ON_INVALID_PROPERTY_PATH;
122 }
123
124 $readInfoExtractor = $writeInfoExtractor;
125 $writeInfoExtractor = 4 < \func_num_args() ? func_get_arg(4) : null;
126 }
127
128 if (null !== $readInfoExtractor && !$readInfoExtractor instanceof PropertyReadInfoExtractorInterface) {
129 throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be null or an instance of "%s", "%s" given.', __METHOD__, PropertyReadInfoExtractorInterface::class, get_debug_type($readInfoExtractor)));
130 }
131
132 if (null !== $writeInfoExtractor && !$writeInfoExtractor instanceof PropertyWriteInfoExtractorInterface) {
133 throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be null or an instance of "%s", "%s" given.', __METHOD__, PropertyWriteInfoExtractorInterface::class, get_debug_type($writeInfoExtractor)));
134 }
135
136 $this->magicMethodsFlags = $magicMethods;
137 $this->ignoreInvalidIndices = 0 === ($throw & self::THROW_ON_INVALID_INDEX);
138 $this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool; // Replace the NullAdapter by the null value
139 $this->ignoreInvalidProperty = 0 === ($throw & self::THROW_ON_INVALID_PROPERTY_PATH);
140 $this->readInfoExtractor = $readInfoExtractor ?? new ReflectionExtractor([], null, null, false);
141 $this->writeInfoExtractor = $writeInfoExtractor ?? new ReflectionExtractor(['set'], null, null, false);
142 }
143
144 /**
145 * {@inheritdoc}
146 */
147 public function getValue($objectOrArray, $propertyPath)
148 {
149 $zval = [
150 self::VALUE => $objectOrArray,
151 ];
152
153 if (\is_object($objectOrArray) && (false === strpbrk((string) $propertyPath, '.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray, $propertyPath))) {
154 return $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty)[self::VALUE];
155 }
156
157 $propertyPath = $this->getPropertyPath($propertyPath);
158
159 $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
160
161 return $propertyValues[\count($propertyValues) - 1][self::VALUE];
162 }
163
164 /**
165 * {@inheritdoc}
166 */
167 public function setValue(&$objectOrArray, $propertyPath, $value)
168 {
169 if (\is_object($objectOrArray) && (false === strpbrk((string) $propertyPath, '.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray, $propertyPath))) {
170 $zval = [
171 self::VALUE => $objectOrArray,
172 ];
173
174 try {
175 $this->writeProperty($zval, $propertyPath, $value);
176
177 return;
178 } catch (\TypeError $e) {
179 self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e);
180 // It wasn't thrown in this class so rethrow it
181 throw $e;
182 }
183 }
184
185 $propertyPath = $this->getPropertyPath($propertyPath);
186
187 $zval = [
188 self::VALUE => $objectOrArray,
189 self::REF => &$objectOrArray,
190 ];
191 $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
192 $overwrite = true;
193
194 try {
195 for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) {
196 $zval = $propertyValues[$i];
197 unset($propertyValues[$i]);
198
199 // You only need set value for current element if:
200 // 1. it's the parent of the last index element
201 // OR
202 // 2. its child is not passed by reference
203 //
204 // This may avoid unnecessary value setting process for array elements.
205 // For example:
206 // '[a][b][c]' => 'old-value'
207 // If you want to change its value to 'new-value',
208 // you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]'
209 if ($overwrite) {
210 $property = $propertyPath->getElement($i);
211
212 if ($propertyPath->isIndex($i)) {
213 if ($overwrite = !isset($zval[self::REF])) {
214 $ref = &$zval[self::REF];
215 $ref = $zval[self::VALUE];
216 }
217 $this->writeIndex($zval, $property, $value);
218 if ($overwrite) {
219 $zval[self::VALUE] = $zval[self::REF];
220 }
221 } else {
222 $this->writeProperty($zval, $property, $value);
223 }
224
225 // if current element is an object
226 // OR
227 // if current element's reference chain is not broken - current element
228 // as well as all its ancients in the property path are all passed by reference,
229 // then there is no need to continue the value setting process
230 if (\is_object($zval[self::VALUE]) || isset($zval[self::IS_REF_CHAINED])) {
231 break;
232 }
233 }
234
235 $value = $zval[self::VALUE];
236 }
237 } catch (\TypeError $e) {
238 self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e);
239
240 // It wasn't thrown in this class so rethrow it
241 throw $e;
242 }
243 }
244
245 private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, ?\Throwable $previous = null): void
246 {
247 if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) {
248 return;
249 }
250
251 if (\PHP_VERSION_ID < 80000) {
252 if (preg_match('/^Typed property \S+::\$\S+ must be (\S+), (\S+) used$/', $message, $matches)) {
253 [, $expectedType, $actualType] = $matches;
254
255 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous);
256 }
257
258 if (!str_starts_with($message, 'Argument ')) {
259 return;
260 }
261
262 $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface '));
263 $pos += \strlen($delim);
264 $j = strpos($message, ',', $pos);
265 $type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2);
266 $message = substr($message, $pos, $j - $pos);
267
268 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath), 0, $previous);
269 }
270
271 if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) {
272 [, $expectedType, $actualType] = $matches;
273
274 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous);
275 }
276 if (preg_match('/^Cannot assign (\S+) to property \S+::\$\S+ of type (\S+)$/', $message, $matches)) {
277 [, $actualType, $expectedType] = $matches;
278
279 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous);
280 }
281 }
282
283 /**
284 * {@inheritdoc}
285 */
286 public function isReadable($objectOrArray, $propertyPath)
287 {
288 if (!$propertyPath instanceof PropertyPathInterface) {
289 $propertyPath = new PropertyPath($propertyPath);
290 }
291
292 try {
293 $zval = [
294 self::VALUE => $objectOrArray,
295 ];
296
297 // handle stdClass with properties with a dot in the name
298 if ($objectOrArray instanceof \stdClass && str_contains($propertyPath, '.') && property_exists($objectOrArray, $propertyPath)) {
299 $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty);
300 } else {
301 $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
302 }
303
304 return true;
305 } catch (AccessException $e) {
306 return false;
307 } catch (UnexpectedTypeException $e) {
308 return false;
309 }
310 }
311
312 /**
313 * {@inheritdoc}
314 */
315 public function isWritable($objectOrArray, $propertyPath)
316 {
317 $propertyPath = $this->getPropertyPath($propertyPath);
318
319 try {
320 $zval = [
321 self::VALUE => $objectOrArray,
322 ];
323
324 // handle stdClass with properties with a dot in the name
325 if ($objectOrArray instanceof \stdClass && str_contains($propertyPath, '.') && property_exists($objectOrArray, $propertyPath)) {
326 $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty);
327
328 return true;
329 }
330
331 $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
332
333 for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) {
334 $zval = $propertyValues[$i];
335 unset($propertyValues[$i]);
336
337 if ($propertyPath->isIndex($i)) {
338 if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
339 return false;
340 }
341 } elseif (!\is_object($zval[self::VALUE]) || !$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) {
342 return false;
343 }
344
345 if (\is_object($zval[self::VALUE])) {
346 return true;
347 }
348 }
349
350 return true;
351 } catch (AccessException $e) {
352 return false;
353 } catch (UnexpectedTypeException $e) {
354 return false;
355 }
356 }
357
358 /**
359 * Reads the path from an object up to a given path index.
360 *
361 * @throws UnexpectedTypeException if a value within the path is neither object nor array
362 * @throws NoSuchIndexException If a non-existing index is accessed
363 */
364 private function readPropertiesUntil(array $zval, PropertyPathInterface $propertyPath, int $lastIndex, bool $ignoreInvalidIndices = true): array
365 {
366 if (!\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
367 throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, 0);
368 }
369
370 // Add the root object to the list
371 $propertyValues = [$zval];
372
373 for ($i = 0; $i < $lastIndex; ++$i) {
374 $property = $propertyPath->getElement($i);
375 $isIndex = $propertyPath->isIndex($i);
376
377 if ($isIndex) {
378 // Create missing nested arrays on demand
379 if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) ||
380 (\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property, $zval[self::VALUE]))
381 ) {
382 if (!$ignoreInvalidIndices) {
383 if (!\is_array($zval[self::VALUE])) {
384 if (!$zval[self::VALUE] instanceof \Traversable) {
385 throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath));
386 }
387
388 $zval[self::VALUE] = iterator_to_array($zval[self::VALUE]);
389 }
390
391 throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".', $property, (string) $propertyPath, print_r(array_keys($zval[self::VALUE]), true)));
392 }
393
394 if ($i + 1 < $propertyPath->getLength()) {
395 if (isset($zval[self::REF])) {
396 $zval[self::VALUE][$property] = [];
397 $zval[self::REF] = $zval[self::VALUE];
398 } else {
399 $zval[self::VALUE] = [$property => []];
400 }
401 }
402 }
403
404 $zval = $this->readIndex($zval, $property);
405 } else {
406 $zval = $this->readProperty($zval, $property, $this->ignoreInvalidProperty);
407 }
408
409 // the final value of the path must not be validated
410 if ($i + 1 < $propertyPath->getLength() && !\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
411 throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1);
412 }
413
414 if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) {
415 // Set the IS_REF_CHAINED flag to true if:
416 // current property is passed by reference and
417 // it is the first element in the property path or
418 // the IS_REF_CHAINED flag of its parent element is true
419 // Basically, this flag is true only when the reference chain from the top element to current element is not broken
420 $zval[self::IS_REF_CHAINED] = true;
421 }
422
423 $propertyValues[] = $zval;
424 }
425
426 return $propertyValues;
427 }
428
429 /**
430 * Reads a key from an array-like structure.
431 *
432 * @param string|int $index The key to read
433 *
434 * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
435 */
436 private function readIndex(array $zval, $index): array
437 {
438 if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
439 throw new NoSuchIndexException(sprintf('Cannot read index "%s" from object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_debug_type($zval[self::VALUE])));
440 }
441
442 $result = self::RESULT_PROTO;
443
444 if (isset($zval[self::VALUE][$index])) {
445 $result[self::VALUE] = $zval[self::VALUE][$index];
446
447 if (!isset($zval[self::REF])) {
448 // Save creating references when doing read-only lookups
449 } elseif (\is_array($zval[self::VALUE])) {
450 $result[self::REF] = &$zval[self::REF][$index];
451 } elseif (\is_object($result[self::VALUE])) {
452 $result[self::REF] = $result[self::VALUE];
453 }
454 }
455
456 return $result;
457 }
458
459 /**
460 * Reads the value of a property from an object.
461 *
462 * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public
463 */
464 private function readProperty(array $zval, string $property, bool $ignoreInvalidProperty = false): array
465 {
466 if (!\is_object($zval[self::VALUE])) {
467 throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.', $property));
468 }
469
470 $result = self::RESULT_PROTO;
471 $object = $zval[self::VALUE];
472 $class = \get_class($object);
473 $access = $this->getReadInfo($class, $property);
474
475 if (null !== $access) {
476 $name = $access->getName();
477 $type = $access->getType();
478
479 try {
480 if (PropertyReadInfo::TYPE_METHOD === $type) {
481 try {
482 $result[self::VALUE] = $object->$name();
483 } catch (\TypeError $e) {
484 [$trace] = $e->getTrace();
485
486 // handle uninitialized properties in PHP >= 7
487 if (__FILE__ === ($trace['file'] ?? null)
488 && $name === $trace['function']
489 && $object instanceof $trace['class']
490 && preg_match('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/', $e->getMessage(), $matches)
491 ) {
492 throw new UninitializedPropertyException(sprintf('The method "%s::%s()" returned "null", but expected type "%3$s". Did you forget to initialize a property or to make the return type nullable using "?%3$s"?', get_debug_type($object), $name, $matches[1]), 0, $e);
493 }
494
495 throw $e;
496 }
497 } elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
498 if ($access->canBeReference() && !isset($object->$name) && !\array_key_exists($name, (array) $object) && (\PHP_VERSION_ID < 70400 || !(new \ReflectionProperty($class, $name))->hasType())) {
499 throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
500 }
501
502 $result[self::VALUE] = $object->$name;
503
504 if (isset($zval[self::REF]) && $access->canBeReference()) {
505 $result[self::REF] = &$object->$name;
506 }
507 }
508 } catch (\Error $e) {
509 // handle uninitialized properties in PHP >= 7.4
510 if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\\@]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
511 $r = new \ReflectionProperty(str_contains($matches[1], '@anonymous') ? $class : $matches[1], $matches[2]);
512 $type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
513
514 throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not readable because it is typed "%s". You should initialize it or declare a default value instead.', $matches[1], $r->getName(), $type), 0, $e);
515 }
516
517 throw $e;
518 }
519 } elseif (property_exists($object, $property) && \array_key_exists($property, (array) $object)) {
520 $result[self::VALUE] = $object->$property;
521 if (isset($zval[self::REF])) {
522 $result[self::REF] = &$object->$property;
523 }
524 } elseif (!$ignoreInvalidProperty) {
525 throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class));
526 }
527
528 // Objects are always passed around by reference
529 if (isset($zval[self::REF]) && \is_object($result[self::VALUE])) {
530 $result[self::REF] = $result[self::VALUE];
531 }
532
533 return $result;
534 }
535
536 /**
537 * Guesses how to read the property value.
538 */
539 private function getReadInfo(string $class, string $property): ?PropertyReadInfo
540 {
541 $key = str_replace('\\', '.', $class).'..'.$property;
542
543 if (isset($this->readPropertyCache[$key])) {
544 return $this->readPropertyCache[$key];
545 }
546
547 if ($this->cacheItemPool) {
548 $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.rawurlencode($key));
549 if ($item->isHit()) {
550 return $this->readPropertyCache[$key] = $item->get();
551 }
552 }
553
554 $accessor = $this->readInfoExtractor->getReadInfo($class, $property, [
555 'enable_getter_setter_extraction' => true,
556 'enable_magic_methods_extraction' => $this->magicMethodsFlags,
557 'enable_constructor_extraction' => false,
558 ]);
559
560 if (isset($item)) {
561 $this->cacheItemPool->save($item->set($accessor));
562 }
563
564 return $this->readPropertyCache[$key] = $accessor;
565 }
566
567 /**
568 * Sets the value of an index in a given array-accessible value.
569 *
570 * @param string|int $index The index to write at
571 * @param mixed $value The value to write
572 *
573 * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
574 */
575 private function writeIndex(array $zval, $index, $value)
576 {
577 if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
578 throw new NoSuchIndexException(sprintf('Cannot modify index "%s" in object of type "%s" because it doesn\'t implement \ArrayAccess.', $index, get_debug_type($zval[self::VALUE])));
579 }
580
581 $zval[self::REF][$index] = $value;
582 }
583
584 /**
585 * Sets the value of a property in the given object.
586 *
587 * @param mixed $value The value to write
588 *
589 * @throws NoSuchPropertyException if the property does not exist or is not public
590 */
591 private function writeProperty(array $zval, string $property, $value)
592 {
593 if (!\is_object($zval[self::VALUE])) {
594 throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?', $property));
595 }
596
597 $object = $zval[self::VALUE];
598 $class = \get_class($object);
599 $mutator = $this->getWriteInfo($class, $property, $value);
600
601 if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
602 $type = $mutator->getType();
603
604 if (PropertyWriteInfo::TYPE_METHOD === $type) {
605 $object->{$mutator->getName()}($value);
606 } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
607 $object->{$mutator->getName()} = $value;
608 } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
609 $this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo());
610 }
611 } elseif ($object instanceof \stdClass && property_exists($object, $property)) {
612 $object->$property = $value;
613 } elseif (!$this->ignoreInvalidProperty) {
614 if ($mutator->hasErrors()) {
615 throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.');
616 }
617
618 throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object)));
619 }
620 }
621
622 /**
623 * Adjusts a collection-valued property by calling add*() and remove*() methods.
624 */
625 private function writeCollection(array $zval, string $property, iterable $collection, PropertyWriteInfo $addMethod, PropertyWriteInfo $removeMethod)
626 {
627 // At this point the add and remove methods have been found
628 $previousValue = $this->readProperty($zval, $property);
629 $previousValue = $previousValue[self::VALUE];
630
631 $removeMethodName = $removeMethod->getName();
632 $addMethodName = $addMethod->getName();
633
634 if ($previousValue instanceof \Traversable) {
635 $previousValue = iterator_to_array($previousValue);
636 }
637 if ($previousValue && \is_array($previousValue)) {
638 if (\is_object($collection)) {
639 $collection = iterator_to_array($collection);
640 }
641 foreach ($previousValue as $key => $item) {
642 if (!\in_array($item, $collection, true)) {
643 unset($previousValue[$key]);
644 $zval[self::VALUE]->$removeMethodName($item);
645 }
646 }
647 } else {
648 $previousValue = false;
649 }
650
651 foreach ($collection as $item) {
652 if (!$previousValue || !\in_array($item, $previousValue, true)) {
653 $zval[self::VALUE]->$addMethodName($item);
654 }
655 }
656 }
657
658 private function getWriteInfo(string $class, string $property, $value): PropertyWriteInfo
659 {
660 $useAdderAndRemover = is_iterable($value);
661 $key = str_replace('\\', '.', $class).'..'.$property.'..'.(int) $useAdderAndRemover;
662
663 if (isset($this->writePropertyCache[$key])) {
664 return $this->writePropertyCache[$key];
665 }
666
667 if ($this->cacheItemPool) {
668 $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.rawurlencode($key));
669 if ($item->isHit()) {
670 return $this->writePropertyCache[$key] = $item->get();
671 }
672 }
673
674 $mutator = $this->writeInfoExtractor->getWriteInfo($class, $property, [
675 'enable_getter_setter_extraction' => true,
676 'enable_magic_methods_extraction' => $this->magicMethodsFlags,
677 'enable_constructor_extraction' => false,
678 'enable_adder_remover_extraction' => $useAdderAndRemover,
679 ]);
680
681 if (isset($item)) {
682 $this->cacheItemPool->save($item->set($mutator));
683 }
684
685 return $this->writePropertyCache[$key] = $mutator;
686 }
687
688 /**
689 * Returns whether a property is writable in the given object.
690 */
691 private function isPropertyWritable(object $object, string $property): bool
692 {
693 $mutatorForArray = $this->getWriteInfo(\get_class($object), $property, []);
694
695 if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || ($object instanceof \stdClass && property_exists($object, $property))) {
696 return true;
697 }
698
699 $mutator = $this->getWriteInfo(\get_class($object), $property, '');
700
701 return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || ($object instanceof \stdClass && property_exists($object, $property));
702 }
703
704 /**
705 * Gets a PropertyPath instance and caches it.
706 *
707 * @param string|PropertyPath $propertyPath
708 */
709 private function getPropertyPath($propertyPath): PropertyPath
710 {
711 if ($propertyPath instanceof PropertyPathInterface) {
712 // Don't call the copy constructor has it is not needed here
713 return $propertyPath;
714 }
715
716 if (isset($this->propertyPathCache[$propertyPath])) {
717 return $this->propertyPathCache[$propertyPath];
718 }
719
720 if ($this->cacheItemPool) {
721 $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH.rawurlencode($propertyPath));
722 if ($item->isHit()) {
723 return $this->propertyPathCache[$propertyPath] = $item->get();
724 }
725 }
726
727 $propertyPathInstance = new PropertyPath($propertyPath);
728 if (isset($item)) {
729 $item->set($propertyPathInstance);
730 $this->cacheItemPool->save($item);
731 }
732
733 return $this->propertyPathCache[$propertyPath] = $propertyPathInstance;
734 }
735
736 /**
737 * Creates the APCu adapter if applicable.
738 *
739 * @return AdapterInterface
740 *
741 * @throws \LogicException When the Cache Component isn't available
742 */
743 public static function createCache(string $namespace, int $defaultLifetime, string $version, ?LoggerInterface $logger = null)
744 {
745 if (!class_exists(ApcuAdapter::class)) {
746 throw new \LogicException(sprintf('The Symfony Cache component must be installed to use "%s()".', __METHOD__));
747 }
748
749 if (!ApcuAdapter::isSupported()) {
750 return new NullAdapter();
751 }
752
753 $apcu = new ApcuAdapter($namespace, $defaultLifetime / 5, $version);
754 if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
755 $apcu->setLogger(new NullLogger());
756 } elseif (null !== $logger) {
757 $apcu->setLogger($logger);
758 }
759
760 return $apcu;
761 }
762 }
763