vendor/symfony/property-access/PropertyAccessor.php line 108

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