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

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'.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray$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'.[') || $objectOrArray instanceof \stdClass && property_exists($objectOrArray$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.             // handle stdClass with properties with a dot in the name
  248.             if ($objectOrArray instanceof \stdClass && str_contains($propertyPath'.')  && property_exists($objectOrArray$propertyPath)) {
  249.                 $this->readProperty($zval$propertyPath$this->ignoreInvalidProperty);
  250.             } else {
  251.                 $this->readPropertiesUntil($zval$propertyPath$propertyPath->getLength(), $this->ignoreInvalidIndices);
  252.             }
  253.             return true;
  254.         } catch (AccessException $e) {
  255.             return false;
  256.         } catch (UnexpectedTypeException $e) {
  257.             return false;
  258.         }
  259.     }
  260.     /**
  261.      * {@inheritdoc}
  262.      */
  263.     public function isWritable($objectOrArray$propertyPath)
  264.     {
  265.         $propertyPath $this->getPropertyPath($propertyPath);
  266.         try {
  267.             $zval = [
  268.                 self::VALUE => $objectOrArray,
  269.             ];
  270.             // handle stdClass with properties with a dot in the name
  271.             if ($objectOrArray instanceof \stdClass && str_contains($propertyPath'.') && property_exists($objectOrArray$propertyPath)) {
  272.                 $this->readProperty($zval$propertyPath$this->ignoreInvalidProperty);
  273.                 return true;
  274.             }
  275.             $propertyValues $this->readPropertiesUntil($zval$propertyPath$propertyPath->getLength() - 1);
  276.             for ($i = \count($propertyValues) - 1<= $i; --$i) {
  277.                 $zval $propertyValues[$i];
  278.                 unset($propertyValues[$i]);
  279.                 if ($propertyPath->isIndex($i)) {
  280.                     if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
  281.                         return false;
  282.                     }
  283.                 } elseif (!\is_object($zval[self::VALUE]) || !$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) {
  284.                     return false;
  285.                 }
  286.                 if (\is_object($zval[self::VALUE])) {
  287.                     return true;
  288.                 }
  289.             }
  290.             return true;
  291.         } catch (AccessException $e) {
  292.             return false;
  293.         } catch (UnexpectedTypeException $e) {
  294.             return false;
  295.         }
  296.     }
  297.     /**
  298.      * Reads the path from an object up to a given path index.
  299.      *
  300.      * @throws UnexpectedTypeException if a value within the path is neither object nor array
  301.      * @throws NoSuchIndexException    If a non-existing index is accessed
  302.      */
  303.     private function readPropertiesUntil(array $zvalPropertyPathInterface $propertyPathint $lastIndexbool $ignoreInvalidIndices true): array
  304.     {
  305.         if (!\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
  306.             throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath0);
  307.         }
  308.         // Add the root object to the list
  309.         $propertyValues = [$zval];
  310.         for ($i 0$i $lastIndex; ++$i) {
  311.             $property $propertyPath->getElement($i);
  312.             $isIndex $propertyPath->isIndex($i);
  313.             if ($isIndex) {
  314.                 // Create missing nested arrays on demand
  315.                 if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) ||
  316.                     (\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property$zval[self::VALUE]))
  317.                 ) {
  318.                     if (!$ignoreInvalidIndices) {
  319.                         if (!\is_array($zval[self::VALUE])) {
  320.                             if (!$zval[self::VALUE] instanceof \Traversable) {
  321.                                 throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".'$property, (string) $propertyPath));
  322.                             }
  323.                             $zval[self::VALUE] = iterator_to_array($zval[self::VALUE]);
  324.                         }
  325.                         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)));
  326.                     }
  327.                     if ($i $propertyPath->getLength()) {
  328.                         if (isset($zval[self::REF])) {
  329.                             $zval[self::VALUE][$property] = [];
  330.                             $zval[self::REF] = $zval[self::VALUE];
  331.                         } else {
  332.                             $zval[self::VALUE] = [$property => []];
  333.                         }
  334.                     }
  335.                 }
  336.                 $zval $this->readIndex($zval$property);
  337.             } else {
  338.                 $zval $this->readProperty($zval$property$this->ignoreInvalidProperty);
  339.             }
  340.             // the final value of the path must not be validated
  341.             if ($i $propertyPath->getLength() && !\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
  342.                 throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath$i 1);
  343.             }
  344.             if (isset($zval[self::REF]) && (=== $i || isset($propertyValues[$i 1][self::IS_REF_CHAINED]))) {
  345.                 // Set the IS_REF_CHAINED flag to true if:
  346.                 // current property is passed by reference and
  347.                 // it is the first element in the property path or
  348.                 // the IS_REF_CHAINED flag of its parent element is true
  349.                 // Basically, this flag is true only when the reference chain from the top element to current element is not broken
  350.                 $zval[self::IS_REF_CHAINED] = true;
  351.             }
  352.             $propertyValues[] = $zval;
  353.         }
  354.         return $propertyValues;
  355.     }
  356.     /**
  357.      * Reads a key from an array-like structure.
  358.      *
  359.      * @param string|int $index The key to read
  360.      *
  361.      * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
  362.      */
  363.     private function readIndex(array $zval$index): array
  364.     {
  365.         if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
  366.             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])));
  367.         }
  368.         $result self::RESULT_PROTO;
  369.         if (isset($zval[self::VALUE][$index])) {
  370.             $result[self::VALUE] = $zval[self::VALUE][$index];
  371.             if (!isset($zval[self::REF])) {
  372.                 // Save creating references when doing read-only lookups
  373.             } elseif (\is_array($zval[self::VALUE])) {
  374.                 $result[self::REF] = &$zval[self::REF][$index];
  375.             } elseif (\is_object($result[self::VALUE])) {
  376.                 $result[self::REF] = $result[self::VALUE];
  377.             }
  378.         }
  379.         return $result;
  380.     }
  381.     /**
  382.      * Reads the value of a property from an object.
  383.      *
  384.      * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public
  385.      */
  386.     private function readProperty(array $zvalstring $propertybool $ignoreInvalidProperty false): array
  387.     {
  388.         if (!\is_object($zval[self::VALUE])) {
  389.             throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.'$property));
  390.         }
  391.         $result self::RESULT_PROTO;
  392.         $object $zval[self::VALUE];
  393.         $class = \get_class($object);
  394.         $access $this->getReadInfo($class$property);
  395.         if (null !== $access) {
  396.             $name $access->getName();
  397.             $type $access->getType();
  398.             try {
  399.                 if (PropertyReadInfo::TYPE_METHOD === $type) {
  400.                     try {
  401.                         $result[self::VALUE] = $object->$name();
  402.                     } catch (\TypeError $e) {
  403.                         [$trace] = $e->getTrace();
  404.                         // handle uninitialized properties in PHP >= 7
  405.                         if (__FILE__ === ($trace['file'] ?? null)
  406.                             && $name === $trace['function']
  407.                             && $object instanceof $trace['class']
  408.                             && preg_match('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/'$e->getMessage(), $matches)
  409.                         ) {
  410.                             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);
  411.                         }
  412.                         throw $e;
  413.                     }
  414.                 } elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
  415.                     if ($access->canBeReference() && !isset($object->$name) && !\array_key_exists($name, (array) $object) && (\PHP_VERSION_ID 70400 || !(new \ReflectionProperty($class$name))->hasType())) {
  416.                         throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.'$class$name));
  417.                     }
  418.                     $result[self::VALUE] = $object->$name;
  419.                     if (isset($zval[self::REF]) && $access->canBeReference()) {
  420.                         $result[self::REF] = &$object->$name;
  421.                     }
  422.                 }
  423.             } catch (\Error $e) {
  424.                 // handle uninitialized properties in PHP >= 7.4
  425.                 if (\PHP_VERSION_ID >= 70400 && preg_match('/^Typed property ([\w\\\\@]+)::\$(\w+) must not be accessed before initialization$/'$e->getMessage(), $matches)) {
  426.                     $r = new \ReflectionProperty(str_contains($matches[1], '@anonymous') ? $class $matches[1], $matches[2]);
  427.                     $type = ($type $r->getType()) instanceof \ReflectionNamedType $type->getName() : (string) $type;
  428.                     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);
  429.                 }
  430.                 throw $e;
  431.             }
  432.         } elseif (property_exists($object$property) && \array_key_exists($property, (array) $object)) {
  433.             $result[self::VALUE] = $object->$property;
  434.             if (isset($zval[self::REF])) {
  435.                 $result[self::REF] = &$object->$property;
  436.             }
  437.         } elseif (!$ignoreInvalidProperty) {
  438.             throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".'$property$class));
  439.         }
  440.         // Objects are always passed around by reference
  441.         if (isset($zval[self::REF]) && \is_object($result[self::VALUE])) {
  442.             $result[self::REF] = $result[self::VALUE];
  443.         }
  444.         return $result;
  445.     }
  446.     /**
  447.      * Guesses how to read the property value.
  448.      */
  449.     private function getReadInfo(string $classstring $property): ?PropertyReadInfo
  450.     {
  451.         $key str_replace('\\''.'$class).'..'.$property;
  452.         if (isset($this->readPropertyCache[$key])) {
  453.             return $this->readPropertyCache[$key];
  454.         }
  455.         if ($this->cacheItemPool) {
  456.             $item $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.rawurlencode($key));
  457.             if ($item->isHit()) {
  458.                 return $this->readPropertyCache[$key] = $item->get();
  459.             }
  460.         }
  461.         $accessor $this->readInfoExtractor->getReadInfo($class$property, [
  462.             'enable_getter_setter_extraction' => true,
  463.             'enable_magic_methods_extraction' => $this->magicMethodsFlags,
  464.             'enable_constructor_extraction' => false,
  465.         ]);
  466.         if (isset($item)) {
  467.             $this->cacheItemPool->save($item->set($accessor));
  468.         }
  469.         return $this->readPropertyCache[$key] = $accessor;
  470.     }
  471.     /**
  472.      * Sets the value of an index in a given array-accessible value.
  473.      *
  474.      * @param string|int $index The index to write at
  475.      * @param mixed      $value The value to write
  476.      *
  477.      * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
  478.      */
  479.     private function writeIndex(array $zval$index$value)
  480.     {
  481.         if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
  482.             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])));
  483.         }
  484.         $zval[self::REF][$index] = $value;
  485.     }
  486.     /**
  487.      * Sets the value of a property in the given object.
  488.      *
  489.      * @param mixed $value The value to write
  490.      *
  491.      * @throws NoSuchPropertyException if the property does not exist or is not public
  492.      */
  493.     private function writeProperty(array $zvalstring $property$value)
  494.     {
  495.         if (!\is_object($zval[self::VALUE])) {
  496.             throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?'$property));
  497.         }
  498.         $object $zval[self::VALUE];
  499.         $class = \get_class($object);
  500.         $mutator $this->getWriteInfo($class$property$value);
  501.         if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
  502.             $type $mutator->getType();
  503.             if (PropertyWriteInfo::TYPE_METHOD === $type) {
  504.                 $object->{$mutator->getName()}($value);
  505.             } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
  506.                 $object->{$mutator->getName()} = $value;
  507.             } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
  508.                 $this->writeCollection($zval$property$value$mutator->getAdderInfo(), $mutator->getRemoverInfo());
  509.             }
  510.         } elseif ($object instanceof \stdClass && property_exists($object$property)) {
  511.             $object->$property $value;
  512.         } elseif (!$this->ignoreInvalidProperty) {
  513.             if ($mutator->hasErrors()) {
  514.                 throw new NoSuchPropertyException(implode('. '$mutator->getErrors()).'.');
  515.             }
  516.             throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".'$propertyget_debug_type($object)));
  517.         }
  518.     }
  519.     /**
  520.      * Adjusts a collection-valued property by calling add*() and remove*() methods.
  521.      */
  522.     private function writeCollection(array $zvalstring $propertyiterable $collectionPropertyWriteInfo $addMethodPropertyWriteInfo $removeMethod)
  523.     {
  524.         // At this point the add and remove methods have been found
  525.         $previousValue $this->readProperty($zval$property);
  526.         $previousValue $previousValue[self::VALUE];
  527.         $removeMethodName $removeMethod->getName();
  528.         $addMethodName $addMethod->getName();
  529.         if ($previousValue instanceof \Traversable) {
  530.             $previousValue iterator_to_array($previousValue);
  531.         }
  532.         if ($previousValue && \is_array($previousValue)) {
  533.             if (\is_object($collection)) {
  534.                 $collection iterator_to_array($collection);
  535.             }
  536.             foreach ($previousValue as $key => $item) {
  537.                 if (!\in_array($item$collectiontrue)) {
  538.                     unset($previousValue[$key]);
  539.                     $zval[self::VALUE]->$removeMethodName($item);
  540.                 }
  541.             }
  542.         } else {
  543.             $previousValue false;
  544.         }
  545.         foreach ($collection as $item) {
  546.             if (!$previousValue || !\in_array($item$previousValuetrue)) {
  547.                 $zval[self::VALUE]->$addMethodName($item);
  548.             }
  549.         }
  550.     }
  551.     private function getWriteInfo(string $classstring $property$value): PropertyWriteInfo
  552.     {
  553.         $useAdderAndRemover is_iterable($value);
  554.         $key str_replace('\\''.'$class).'..'.$property.'..'.(int) $useAdderAndRemover;
  555.         if (isset($this->writePropertyCache[$key])) {
  556.             return $this->writePropertyCache[$key];
  557.         }
  558.         if ($this->cacheItemPool) {
  559.             $item $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.rawurlencode($key));
  560.             if ($item->isHit()) {
  561.                 return $this->writePropertyCache[$key] = $item->get();
  562.             }
  563.         }
  564.         $mutator $this->writeInfoExtractor->getWriteInfo($class$property, [
  565.             'enable_getter_setter_extraction' => true,
  566.             'enable_magic_methods_extraction' => $this->magicMethodsFlags,
  567.             'enable_constructor_extraction' => false,
  568.             'enable_adder_remover_extraction' => $useAdderAndRemover,
  569.         ]);
  570.         if (isset($item)) {
  571.             $this->cacheItemPool->save($item->set($mutator));
  572.         }
  573.         return $this->writePropertyCache[$key] = $mutator;
  574.     }
  575.     /**
  576.      * Returns whether a property is writable in the given object.
  577.      */
  578.     private function isPropertyWritable(object $objectstring $property): bool
  579.     {
  580.         $mutatorForArray $this->getWriteInfo(\get_class($object), $property, []);
  581.         if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || ($object instanceof \stdClass && property_exists($object$property))) {
  582.             return true;
  583.         }
  584.         $mutator $this->getWriteInfo(\get_class($object), $property'');
  585.         return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || ($object instanceof \stdClass && property_exists($object$property));
  586.     }
  587.     /**
  588.      * Gets a PropertyPath instance and caches it.
  589.      *
  590.      * @param string|PropertyPath $propertyPath
  591.      */
  592.     private function getPropertyPath($propertyPath): PropertyPath
  593.     {
  594.         if ($propertyPath instanceof PropertyPathInterface) {
  595.             // Don't call the copy constructor has it is not needed here
  596.             return $propertyPath;
  597.         }
  598.         if (isset($this->propertyPathCache[$propertyPath])) {
  599.             return $this->propertyPathCache[$propertyPath];
  600.         }
  601.         if ($this->cacheItemPool) {
  602.             $item $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH.rawurlencode($propertyPath));
  603.             if ($item->isHit()) {
  604.                 return $this->propertyPathCache[$propertyPath] = $item->get();
  605.             }
  606.         }
  607.         $propertyPathInstance = new PropertyPath($propertyPath);
  608.         if (isset($item)) {
  609.             $item->set($propertyPathInstance);
  610.             $this->cacheItemPool->save($item);
  611.         }
  612.         return $this->propertyPathCache[$propertyPath] = $propertyPathInstance;
  613.     }
  614.     /**
  615.      * Creates the APCu adapter if applicable.
  616.      *
  617.      * @return AdapterInterface
  618.      *
  619.      * @throws \LogicException When the Cache Component isn't available
  620.      */
  621.     public static function createCache(string $namespaceint $defaultLifetimestring $version, ?LoggerInterface $logger null)
  622.     {
  623.         if (!class_exists(ApcuAdapter::class)) {
  624.             throw new \LogicException(sprintf('The Symfony Cache component must be installed to use "%s()".'__METHOD__));
  625.         }
  626.         if (!ApcuAdapter::isSupported()) {
  627.             return new NullAdapter();
  628.         }
  629.         $apcu = new ApcuAdapter($namespace$defaultLifetime 5$version);
  630.         if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
  631.             $apcu->setLogger(new NullLogger());
  632.         } elseif (null !== $logger) {
  633.             $apcu->setLogger($logger);
  634.         }
  635.         return $apcu;
  636.     }
  637. }