PluginProbe
WindPress – Tailwind CSS integration for WordPress / 3.2.85
WindPress – Tailwind CSS integration for WordPress v3.2.85
3.2.90 3.2.89 3.2.88 3.2.87 3.2.86 3.2.85 3.2.84 3.2.83 3.2.82 3.2.81 trunk 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.2 3.0.3 3.0.4 3.0.5 All 144 releases
windpress / vendor / symfony / property-access / PropertyAccessor.php

PropertyAccessor.php in WindPress – Tailwind CSS integration for WordPress 3.2.85, at vendor/symfony/property-access/PropertyAccessor.php

615 lines 32.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 namespace WindPressDeps\Symfony\Component\PropertyAccess;
12
13 use WindPressDeps\Psr\Cache\CacheItemPoolInterface;
14 use WindPressDeps\Psr\Log\LoggerInterface;
15 use WindPressDeps\Psr\Log\NullLogger;
16 use WindPressDeps\Symfony\Component\Cache\Adapter\AdapterInterface;
17 use WindPressDeps\Symfony\Component\Cache\Adapter\ApcuAdapter;
18 use WindPressDeps\Symfony\Component\Cache\Adapter\NullAdapter;
19 use WindPressDeps\Symfony\Component\PropertyAccess\Exception\AccessException;
20 use WindPressDeps\Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
21 use WindPressDeps\Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
22 use WindPressDeps\Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
23 use WindPressDeps\Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
24 use WindPressDeps\Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
25 use WindPressDeps\Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
26 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyReadInfo;
27 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
28 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyWriteInfo;
29 use WindPressDeps\Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
30 /**
31 * Default implementation of {@link PropertyAccessorInterface}.
32 *
33 * @author Bernhard Schussek <bschussek@gmail.com>
34 * @author Kévin Dunglas <dunglas@gmail.com>
35 * @author Nicolas Grekas <p@tchwork.com>
36 */
37 class PropertyAccessor implements PropertyAccessorInterface
38 {
39 /** @var int Allow none of the magic methods */
40 public const DISALLOW_MAGIC_METHODS = ReflectionExtractor::DISALLOW_MAGIC_METHODS;
41 /** @var int Allow magic __get methods */
42 public const MAGIC_GET = ReflectionExtractor::ALLOW_MAGIC_GET;
43 /** @var int Allow magic __set methods */
44 public const MAGIC_SET = ReflectionExtractor::ALLOW_MAGIC_SET;
45 /** @var int Allow magic __call methods */
46 public const MAGIC_CALL = ReflectionExtractor::ALLOW_MAGIC_CALL;
47 public const DO_NOT_THROW = 0;
48 public const THROW_ON_INVALID_INDEX = 1;
49 public const THROW_ON_INVALID_PROPERTY_PATH = 2;
50 private const VALUE = 0;
51 private const REF = 1;
52 private const IS_REF_CHAINED = 2;
53 private const CACHE_PREFIX_READ = 'r';
54 private const CACHE_PREFIX_WRITE = 'w';
55 private const CACHE_PREFIX_PROPERTY_PATH = 'p';
56 private $magicMethodsFlags;
57 private $ignoreInvalidIndices;
58 private $ignoreInvalidProperty;
59 /**
60 * @var CacheItemPoolInterface
61 */
62 private $cacheItemPool;
63 private $propertyPathCache = [];
64 /**
65 * @var PropertyReadInfoExtractorInterface
66 */
67 private $readInfoExtractor;
68 /**
69 * @var PropertyWriteInfoExtractorInterface
70 */
71 private $writeInfoExtractor;
72 private $readPropertyCache = [];
73 private $writePropertyCache = [];
74 private const RESULT_PROTO = [self::VALUE => null];
75 /**
76 * Should not be used by application code. Use
77 * {@link PropertyAccess::createPropertyAccessor()} instead.
78 *
79 * @param int $magicMethods A bitwise combination of the MAGIC_* constants
80 * to specify the allowed magic methods (__get, __set, __call)
81 * or self::DISALLOW_MAGIC_METHODS for none
82 * @param int $throw A bitwise combination of the THROW_* constants
83 * to specify when exceptions should be thrown
84 * @param PropertyReadInfoExtractorInterface $readInfoExtractor
85 * @param PropertyWriteInfoExtractorInterface $writeInfoExtractor
86 */
87 public function __construct($magicMethods = self::MAGIC_GET | self::MAGIC_SET, $throw = self::THROW_ON_INVALID_PROPERTY_PATH, ?CacheItemPoolInterface $cacheItemPool = null, $readInfoExtractor = null, $writeInfoExtractor = null)
88 {
89 if (\is_bool($magicMethods)) {
90 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__);
91 $magicMethods = ($magicMethods ? self::MAGIC_CALL : 0) | self::MAGIC_GET | self::MAGIC_SET;
92 } elseif (!\is_int($magicMethods)) {
93 throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be an integer, "%s" given.', __METHOD__, get_debug_type($readInfoExtractor)));
94 }
95 if (\is_bool($throw)) {
96 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__);
97 $throw = $throw ? self::THROW_ON_INVALID_INDEX : self::DO_NOT_THROW;
98 if (!\is_bool($readInfoExtractor)) {
99 $throw |= self::THROW_ON_INVALID_PROPERTY_PATH;
100 }
101 }
102 if (\is_bool($readInfoExtractor)) {
103 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__);
104 if ($readInfoExtractor) {
105 $throw |= self::THROW_ON_INVALID_PROPERTY_PATH;
106 }
107 $readInfoExtractor = $writeInfoExtractor;
108 $writeInfoExtractor = 4 < \func_num_args() ? func_get_arg(4) : null;
109 }
110 if (null !== $readInfoExtractor && !$readInfoExtractor instanceof PropertyReadInfoExtractorInterface) {
111 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)));
112 }
113 if (null !== $writeInfoExtractor && !$writeInfoExtractor instanceof PropertyWriteInfoExtractorInterface) {
114 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)));
115 }
116 $this->magicMethodsFlags = $magicMethods;
117 $this->ignoreInvalidIndices = 0 === ($throw & self::THROW_ON_INVALID_INDEX);
118 $this->cacheItemPool = $cacheItemPool instanceof NullAdapter ? null : $cacheItemPool;
119 // Replace the NullAdapter by the null value
120 $this->ignoreInvalidProperty = 0 === ($throw & self::THROW_ON_INVALID_PROPERTY_PATH);
121 $this->readInfoExtractor = $readInfoExtractor ?? new ReflectionExtractor([], null, null, \false);
122 $this->writeInfoExtractor = $writeInfoExtractor ?? new ReflectionExtractor(['set'], null, null, \false);
123 }
124 /**
125 * {@inheritdoc}
126 */
127 public function getValue($objectOrArray, $propertyPath)
128 {
129 $zval = [self::VALUE => $objectOrArray];
130 if (\is_object($objectOrArray) && (\false === strpbrk((string) $propertyPath, '.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray, $propertyPath))) {
131 return $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty)[self::VALUE];
132 }
133 $propertyPath = $this->getPropertyPath($propertyPath);
134 $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
135 return $propertyValues[\count($propertyValues) - 1][self::VALUE];
136 }
137 /**
138 * {@inheritdoc}
139 */
140 public function setValue(&$objectOrArray, $propertyPath, $value)
141 {
142 if (\is_object($objectOrArray) && (\false === strpbrk((string) $propertyPath, '.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray, $propertyPath))) {
143 $zval = [self::VALUE => $objectOrArray];
144 try {
145 $this->writeProperty($zval, $propertyPath, $value);
146 return;
147 } catch (\TypeError $e) {
148 self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e);
149 // It wasn't thrown in this class so rethrow it
150 throw $e;
151 }
152 }
153 $propertyPath = $this->getPropertyPath($propertyPath);
154 $zval = [self::VALUE => $objectOrArray, self::REF => &$objectOrArray];
155 $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
156 $overwrite = \true;
157 try {
158 for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) {
159 $zval = $propertyValues[$i];
160 unset($propertyValues[$i]);
161 // You only need set value for current element if:
162 // 1. it's the parent of the last index element
163 // OR
164 // 2. its child is not passed by reference
165 //
166 // This may avoid unnecessary value setting process for array elements.
167 // For example:
168 // '[a][b][c]' => 'old-value'
169 // If you want to change its value to 'new-value',
170 // you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]'
171 if ($overwrite) {
172 $property = $propertyPath->getElement($i);
173 if ($propertyPath->isIndex($i)) {
174 if ($overwrite = !isset($zval[self::REF])) {
175 $ref =& $zval[self::REF];
176 $ref = $zval[self::VALUE];
177 }
178 $this->writeIndex($zval, $property, $value);
179 if ($overwrite) {
180 $zval[self::VALUE] = $zval[self::REF];
181 }
182 } else {
183 $this->writeProperty($zval, $property, $value);
184 }
185 // if current element is an object
186 // OR
187 // if current element's reference chain is not broken - current element
188 // as well as all its ancients in the property path are all passed by reference,
189 // then there is no need to continue the value setting process
190 if (\is_object($zval[self::VALUE]) || isset($zval[self::IS_REF_CHAINED])) {
191 break;
192 }
193 }
194 $value = $zval[self::VALUE];
195 }
196 } catch (\TypeError $e) {
197 self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0, $propertyPath, $e);
198 // It wasn't thrown in this class so rethrow it
199 throw $e;
200 }
201 }
202 private static function throwInvalidArgumentException(string $message, array $trace, int $i, string $propertyPath, ?\Throwable $previous = null): void
203 {
204 if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) {
205 return;
206 }
207 if (\PHP_VERSION_ID < 80000) {
208 if (preg_match('/^Typed property \S+::\$\S+ must be (\S+), (\S+) used$/', $message, $matches)) {
209 [, $expectedType, $actualType] = $matches;
210 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous);
211 }
212 if (!str_starts_with($message, 'Argument ')) {
213 return;
214 }
215 $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface '));
216 $pos += \strlen($delim);
217 $j = strpos($message, ',', $pos);
218 $type = substr($message, 2 + $j, strpos($message, ' given', $j) - $j - 2);
219 $message = substr($message, $pos, $j - $pos);
220 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $message, 'NULL' === $type ? 'null' : $type, $propertyPath), 0, $previous);
221 }
222 if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/', $message, $matches)) {
223 [, $expectedType, $actualType] = $matches;
224 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous);
225 }
226 if (preg_match('/^Cannot assign (\S+) to property \S+::\$\S+ of type (\S+)$/', $message, $matches)) {
227 [, $actualType, $expectedType] = $matches;
228 throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".', $expectedType, 'NULL' === $actualType ? 'null' : $actualType, $propertyPath), 0, $previous);
229 }
230 }
231 /**
232 * {@inheritdoc}
233 */
234 public function isReadable($objectOrArray, $propertyPath)
235 {
236 if (!$propertyPath instanceof PropertyPathInterface) {
237 $propertyPath = new PropertyPath($propertyPath);
238 }
239 try {
240 $zval = [self::VALUE => $objectOrArray];
241 // handle stdClass with properties with a dot in the name
242 if ($objectOrArray instanceof \stdClass && str_contains($propertyPath, '.') && property_exists($objectOrArray, $propertyPath)) {
243 $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty);
244 } else {
245 $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength(), $this->ignoreInvalidIndices);
246 }
247 return \true;
248 } catch (AccessException $e) {
249 return \false;
250 } catch (UnexpectedTypeException $e) {
251 return \false;
252 }
253 }
254 /**
255 * {@inheritdoc}
256 */
257 public function isWritable($objectOrArray, $propertyPath)
258 {
259 $propertyPath = $this->getPropertyPath($propertyPath);
260 try {
261 $zval = [self::VALUE => $objectOrArray];
262 // handle stdClass with properties with a dot in the name
263 if ($objectOrArray instanceof \stdClass && str_contains($propertyPath, '.') && property_exists($objectOrArray, $propertyPath)) {
264 $this->readProperty($zval, $propertyPath, $this->ignoreInvalidProperty);
265 return \true;
266 }
267 $propertyValues = $this->readPropertiesUntil($zval, $propertyPath, $propertyPath->getLength() - 1);
268 for ($i = \count($propertyValues) - 1; 0 <= $i; --$i) {
269 $zval = $propertyValues[$i];
270 unset($propertyValues[$i]);
271 if ($propertyPath->isIndex($i)) {
272 if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
273 return \false;
274 }
275 } elseif (!\is_object($zval[self::VALUE]) || !$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) {
276 return \false;
277 }
278 if (\is_object($zval[self::VALUE])) {
279 return \true;
280 }
281 }
282 return \true;
283 } catch (AccessException $e) {
284 return \false;
285 } catch (UnexpectedTypeException $e) {
286 return \false;
287 }
288 }
289 /**
290 * Reads the path from an object up to a given path index.
291 *
292 * @throws UnexpectedTypeException if a value within the path is neither object nor array
293 * @throws NoSuchIndexException If a non-existing index is accessed
294 */
295 private function readPropertiesUntil(array $zval, PropertyPathInterface $propertyPath, int $lastIndex, bool $ignoreInvalidIndices = \true): array
296 {
297 if (!\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
298 throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, 0);
299 }
300 // Add the root object to the list
301 $propertyValues = [$zval];
302 for ($i = 0; $i < $lastIndex; ++$i) {
303 $property = $propertyPath->getElement($i);
304 $isIndex = $propertyPath->isIndex($i);
305 if ($isIndex) {
306 // Create missing nested arrays on demand
307 if ($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property) || \is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property, $zval[self::VALUE])) {
308 if (!$ignoreInvalidIndices) {
309 if (!\is_array($zval[self::VALUE])) {
310 if (!$zval[self::VALUE] instanceof \Traversable) {
311 throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".', $property, (string) $propertyPath));
312 }
313 $zval[self::VALUE] = iterator_to_array($zval[self::VALUE]);
314 }
315 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)));
316 }
317 if ($i + 1 < $propertyPath->getLength()) {
318 if (isset($zval[self::REF])) {
319 $zval[self::VALUE][$property] = [];
320 $zval[self::REF] = $zval[self::VALUE];
321 } else {
322 $zval[self::VALUE] = [$property => []];
323 }
324 }
325 }
326 $zval = $this->readIndex($zval, $property);
327 } else {
328 $zval = $this->readProperty($zval, $property, $this->ignoreInvalidProperty);
329 }
330 // the final value of the path must not be validated
331 if ($i + 1 < $propertyPath->getLength() && !\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
332 throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath, $i + 1);
333 }
334 if (isset($zval[self::REF]) && (0 === $i || isset($propertyValues[$i - 1][self::IS_REF_CHAINED]))) {
335 // Set the IS_REF_CHAINED flag to true if:
336 // current property is passed by reference and
337 // it is the first element in the property path or
338 // the IS_REF_CHAINED flag of its parent element is true
339 // Basically, this flag is true only when the reference chain from the top element to current element is not broken
340 $zval[self::IS_REF_CHAINED] = \true;
341 }
342 $propertyValues[] = $zval;
343 }
344 return $propertyValues;
345 }
346 /**
347 * Reads a key from an array-like structure.
348 *
349 * @param string|int $index The key to read
350 *
351 * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
352 */
353 private function readIndex(array $zval, $index): array
354 {
355 if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
356 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])));
357 }
358 $result = self::RESULT_PROTO;
359 if (isset($zval[self::VALUE][$index])) {
360 $result[self::VALUE] = $zval[self::VALUE][$index];
361 if (!isset($zval[self::REF])) {
362 // Save creating references when doing read-only lookups
363 } elseif (\is_array($zval[self::VALUE])) {
364 $result[self::REF] =& $zval[self::REF][$index];
365 } elseif (\is_object($result[self::VALUE])) {
366 $result[self::REF] = $result[self::VALUE];
367 }
368 }
369 return $result;
370 }
371 /**
372 * Reads the value of a property from an object.
373 *
374 * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public
375 */
376 private function readProperty(array $zval, string $property, bool $ignoreInvalidProperty = \false): array
377 {
378 if (!\is_object($zval[self::VALUE])) {
379 throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.', $property));
380 }
381 $result = self::RESULT_PROTO;
382 $object = $zval[self::VALUE];
383 $class = \get_class($object);
384 $access = $this->getReadInfo($class, $property);
385 if (null !== $access) {
386 $name = $access->getName();
387 $type = $access->getType();
388 try {
389 if (PropertyReadInfo::TYPE_METHOD === $type) {
390 try {
391 $result[self::VALUE] = $object->{$name}();
392 } catch (\TypeError $e) {
393 [$trace] = $e->getTrace();
394 // handle uninitialized properties in PHP >= 7
395 if (__FILE__ === ($trace['file'] ?? null) && $name === $trace['function'] && $object instanceof $trace['class'] && preg_match('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/', $e->getMessage(), $matches)) {
396 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);
397 }
398 throw $e;
399 }
400 } elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
401 if ($access->canBeReference() && !isset($object->{$name}) && !\array_key_exists($name, (array) $object) && (\PHP_VERSION_ID < 70400 || !(new \ReflectionProperty($class, $name))->hasType())) {
402 throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $class, $name));
403 }
404 $result[self::VALUE] = $object->{$name};
405 if (isset($zval[self::REF]) && $access->canBeReference()) {
406 $result[self::REF] =& $object->{$name};
407 }
408 }
409 } catch (\Error $e) {
410 // handle uninitialized properties in PHP >= 7.4
411 if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\\@]+)::\$(\w+) must not be accessed before initialization$/', $e->getMessage(), $matches)) {
412 $r = new \ReflectionProperty(str_contains($matches[1], '@anonymous') ? $class : $matches[1], $matches[2]);
413 $type = ($type = $r->getType()) instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
414 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);
415 }
416 throw $e;
417 }
418 } elseif (property_exists($object, $property) && \array_key_exists($property, (array) $object)) {
419 $result[self::VALUE] = $object->{$property};
420 if (isset($zval[self::REF])) {
421 $result[self::REF] =& $object->{$property};
422 }
423 } elseif (!$ignoreInvalidProperty) {
424 throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".', $property, $class));
425 }
426 // Objects are always passed around by reference
427 if (isset($zval[self::REF]) && \is_object($result[self::VALUE])) {
428 $result[self::REF] = $result[self::VALUE];
429 }
430 return $result;
431 }
432 /**
433 * Guesses how to read the property value.
434 */
435 private function getReadInfo(string $class, string $property): ?PropertyReadInfo
436 {
437 $key = str_replace('\\', '.', $class) . '..' . $property;
438 if (isset($this->readPropertyCache[$key])) {
439 return $this->readPropertyCache[$key];
440 }
441 if ($this->cacheItemPool) {
442 $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ . rawurlencode($key));
443 if ($item->isHit()) {
444 return $this->readPropertyCache[$key] = $item->get();
445 }
446 }
447 $accessor = $this->readInfoExtractor->getReadInfo($class, $property, ['enable_getter_setter_extraction' => \true, 'enable_magic_methods_extraction' => $this->magicMethodsFlags, 'enable_constructor_extraction' => \false]);
448 if (isset($item)) {
449 $this->cacheItemPool->save($item->set($accessor));
450 }
451 return $this->readPropertyCache[$key] = $accessor;
452 }
453 /**
454 * Sets the value of an index in a given array-accessible value.
455 *
456 * @param string|int $index The index to write at
457 * @param mixed $value The value to write
458 *
459 * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
460 */
461 private function writeIndex(array $zval, $index, $value)
462 {
463 if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
464 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])));
465 }
466 $zval[self::REF][$index] = $value;
467 }
468 /**
469 * Sets the value of a property in the given object.
470 *
471 * @param mixed $value The value to write
472 *
473 * @throws NoSuchPropertyException if the property does not exist or is not public
474 */
475 private function writeProperty(array $zval, string $property, $value)
476 {
477 if (!\is_object($zval[self::VALUE])) {
478 throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?', $property));
479 }
480 $object = $zval[self::VALUE];
481 $class = \get_class($object);
482 $mutator = $this->getWriteInfo($class, $property, $value);
483 if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
484 $type = $mutator->getType();
485 if (PropertyWriteInfo::TYPE_METHOD === $type) {
486 $object->{$mutator->getName()}($value);
487 } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
488 $object->{$mutator->getName()} = $value;
489 } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
490 $this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo());
491 }
492 } elseif ($object instanceof \stdClass && property_exists($object, $property)) {
493 $object->{$property} = $value;
494 } elseif (!$this->ignoreInvalidProperty) {
495 if ($mutator->hasErrors()) {
496 throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()) . '.');
497 }
498 throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object)));
499 }
500 }
501 /**
502 * Adjusts a collection-valued property by calling add*() and remove*() methods.
503 */
504 private function writeCollection(array $zval, string $property, iterable $collection, PropertyWriteInfo $addMethod, PropertyWriteInfo $removeMethod)
505 {
506 // At this point the add and remove methods have been found
507 $previousValue = $this->readProperty($zval, $property);
508 $previousValue = $previousValue[self::VALUE];
509 $removeMethodName = $removeMethod->getName();
510 $addMethodName = $addMethod->getName();
511 if ($previousValue instanceof \Traversable) {
512 $previousValue = iterator_to_array($previousValue);
513 }
514 if ($previousValue && \is_array($previousValue)) {
515 if (\is_object($collection)) {
516 $collection = iterator_to_array($collection);
517 }
518 foreach ($previousValue as $key => $item) {
519 if (!\in_array($item, $collection, \true)) {
520 unset($previousValue[$key]);
521 $zval[self::VALUE]->{$removeMethodName}($item);
522 }
523 }
524 } else {
525 $previousValue = \false;
526 }
527 foreach ($collection as $item) {
528 if (!$previousValue || !\in_array($item, $previousValue, \true)) {
529 $zval[self::VALUE]->{$addMethodName}($item);
530 }
531 }
532 }
533 private function getWriteInfo(string $class, string $property, $value): PropertyWriteInfo
534 {
535 $useAdderAndRemover = is_iterable($value);
536 $key = str_replace('\\', '.', $class) . '..' . $property . '..' . (int) $useAdderAndRemover;
537 if (isset($this->writePropertyCache[$key])) {
538 return $this->writePropertyCache[$key];
539 }
540 if ($this->cacheItemPool) {
541 $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE . rawurlencode($key));
542 if ($item->isHit()) {
543 return $this->writePropertyCache[$key] = $item->get();
544 }
545 }
546 $mutator = $this->writeInfoExtractor->getWriteInfo($class, $property, ['enable_getter_setter_extraction' => \true, 'enable_magic_methods_extraction' => $this->magicMethodsFlags, 'enable_constructor_extraction' => \false, 'enable_adder_remover_extraction' => $useAdderAndRemover]);
547 if (isset($item)) {
548 $this->cacheItemPool->save($item->set($mutator));
549 }
550 return $this->writePropertyCache[$key] = $mutator;
551 }
552 /**
553 * Returns whether a property is writable in the given object.
554 */
555 private function isPropertyWritable(object $object, string $property): bool
556 {
557 $mutatorForArray = $this->getWriteInfo(\get_class($object), $property, []);
558 if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || $object instanceof \stdClass && property_exists($object, $property)) {
559 return \true;
560 }
561 $mutator = $this->getWriteInfo(\get_class($object), $property, '');
562 return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || $object instanceof \stdClass && property_exists($object, $property);
563 }
564 /**
565 * Gets a PropertyPath instance and caches it.
566 *
567 * @param string|PropertyPath $propertyPath
568 */
569 private function getPropertyPath($propertyPath): PropertyPath
570 {
571 if ($propertyPath instanceof PropertyPathInterface) {
572 // Don't call the copy constructor has it is not needed here
573 return $propertyPath;
574 }
575 if (isset($this->propertyPathCache[$propertyPath])) {
576 return $this->propertyPathCache[$propertyPath];
577 }
578 if ($this->cacheItemPool) {
579 $item = $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH . rawurlencode($propertyPath));
580 if ($item->isHit()) {
581 return $this->propertyPathCache[$propertyPath] = $item->get();
582 }
583 }
584 $propertyPathInstance = new PropertyPath($propertyPath);
585 if (isset($item)) {
586 $item->set($propertyPathInstance);
587 $this->cacheItemPool->save($item);
588 }
589 return $this->propertyPathCache[$propertyPath] = $propertyPathInstance;
590 }
591 /**
592 * Creates the APCu adapter if applicable.
593 *
594 * @return AdapterInterface
595 *
596 * @throws \LogicException When the Cache Component isn't available
597 */
598 public static function createCache(string $namespace, int $defaultLifetime, string $version, ?LoggerInterface $logger = null)
599 {
600 if (!class_exists(ApcuAdapter::class)) {
601 throw new \LogicException(sprintf('The Symfony Cache component must be installed to use "%s()".', __METHOD__));
602 }
603 if (!ApcuAdapter::isSupported()) {
604 return new NullAdapter();
605 }
606 $apcu = new ApcuAdapter($namespace, $defaultLifetime / 5, $version);
607 if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
608 $apcu->setLogger(new NullLogger());
609 } elseif (null !== $logger) {
610 $apcu->setLogger($logger);
611 }
612 return $apcu;
613 }
614 }
615