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

  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.      */
  83.     public function __construct(int $magicMethods self::MAGIC_GET self::MAGIC_SETint $throw self::THROW_ON_INVALID_PROPERTY_PATHCacheItemPoolInterface $cacheItemPool nullPropertyReadInfoExtractorInterface $readInfoExtractor nullPropertyWriteInfoExtractorInterface $writeInfoExtractor null)
  84.     {
  85.         $this->magicMethodsFlags $magicMethods;
  86.         $this->ignoreInvalidIndices === ($throw self::THROW_ON_INVALID_INDEX);
  87.         $this->cacheItemPool $cacheItemPool instanceof NullAdapter null $cacheItemPool// Replace the NullAdapter by the null value
  88.         $this->ignoreInvalidProperty === ($throw self::THROW_ON_INVALID_PROPERTY_PATH);
  89.         $this->readInfoExtractor $readInfoExtractor ?? new ReflectionExtractor([], nullnullfalse);
  90.         $this->writeInfoExtractor $writeInfoExtractor ?? new ReflectionExtractor(['set'], nullnullfalse);
  91.     }
  92.     public function getValue(object|array $objectOrArraystring|PropertyPathInterface $propertyPath): mixed
  93.     {
  94.         $zval = [
  95.             self::VALUE => $objectOrArray,
  96.         ];
  97.         if (\is_object($objectOrArray) && false === strpbrk((string) $propertyPath'.[?')) {
  98.             return $this->readProperty($zval$propertyPath$this->ignoreInvalidProperty)[self::VALUE];
  99.         }
  100.         $propertyPath $this->getPropertyPath($propertyPath);
  101.         $propertyValues $this->readPropertiesUntil($zval$propertyPath$propertyPath->getLength(), $this->ignoreInvalidIndices);
  102.         return $propertyValues[\count($propertyValues) - 1][self::VALUE];
  103.     }
  104.     public function setValue(object|array &$objectOrArraystring|PropertyPathInterface $propertyPathmixed $value)
  105.     {
  106.         if (\is_object($objectOrArray) && false === strpbrk((string) $propertyPath'.[')) {
  107.             $zval = [
  108.                 self::VALUE => $objectOrArray,
  109.             ];
  110.             try {
  111.                 $this->writeProperty($zval$propertyPath$value);
  112.                 return;
  113.             } catch (\TypeError $e) {
  114.                 self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0$propertyPath$e);
  115.                 // It wasn't thrown in this class so rethrow it
  116.                 throw $e;
  117.             }
  118.         }
  119.         $propertyPath $this->getPropertyPath($propertyPath);
  120.         $zval = [
  121.             self::VALUE => $objectOrArray,
  122.             self::REF => &$objectOrArray,
  123.         ];
  124.         $propertyValues $this->readPropertiesUntil($zval$propertyPath$propertyPath->getLength() - 1);
  125.         $overwrite true;
  126.         try {
  127.             for ($i \count($propertyValues) - 1<= $i; --$i) {
  128.                 $zval $propertyValues[$i];
  129.                 unset($propertyValues[$i]);
  130.                 // You only need set value for current element if:
  131.                 // 1. it's the parent of the last index element
  132.                 // OR
  133.                 // 2. its child is not passed by reference
  134.                 //
  135.                 // This may avoid unnecessary value setting process for array elements.
  136.                 // For example:
  137.                 // '[a][b][c]' => 'old-value'
  138.                 // If you want to change its value to 'new-value',
  139.                 // you only need set value for '[a][b][c]' and it's safe to ignore '[a][b]' and '[a]'
  140.                 if ($overwrite) {
  141.                     $property $propertyPath->getElement($i);
  142.                     if ($propertyPath->isIndex($i)) {
  143.                         if ($overwrite = !isset($zval[self::REF])) {
  144.                             $ref = &$zval[self::REF];
  145.                             $ref $zval[self::VALUE];
  146.                         }
  147.                         $this->writeIndex($zval$property$value);
  148.                         if ($overwrite) {
  149.                             $zval[self::VALUE] = $zval[self::REF];
  150.                         }
  151.                     } else {
  152.                         $this->writeProperty($zval$property$value);
  153.                     }
  154.                     // if current element is an object
  155.                     // OR
  156.                     // if current element's reference chain is not broken - current element
  157.                     // as well as all its ancients in the property path are all passed by reference,
  158.                     // then there is no need to continue the value setting process
  159.                     if (\is_object($zval[self::VALUE]) || isset($zval[self::IS_REF_CHAINED])) {
  160.                         break;
  161.                     }
  162.                 }
  163.                 $value $zval[self::VALUE];
  164.             }
  165.         } catch (\TypeError $e) {
  166.             self::throwInvalidArgumentException($e->getMessage(), $e->getTrace(), 0$propertyPath$e);
  167.             // It wasn't thrown in this class so rethrow it
  168.             throw $e;
  169.         }
  170.     }
  171.     private static function throwInvalidArgumentException(string $message, array $traceint $istring $propertyPath\Throwable $previous null): void
  172.     {
  173.         if (!isset($trace[$i]['file']) || __FILE__ !== $trace[$i]['file']) {
  174.             return;
  175.         }
  176.         if (preg_match('/^\S+::\S+\(\): Argument #\d+ \(\$\S+\) must be of type (\S+), (\S+) given/'$message$matches)) {
  177.             [, $expectedType$actualType] = $matches;
  178.             throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".'$expectedType'NULL' === $actualType 'null' $actualType$propertyPath), 0$previous);
  179.         }
  180.         if (preg_match('/^Cannot assign (\S+) to property \S+::\$\S+ of type (\S+)$/'$message$matches)) {
  181.             [, $actualType$expectedType] = $matches;
  182.             throw new InvalidArgumentException(sprintf('Expected argument of type "%s", "%s" given at property path "%s".'$expectedType'NULL' === $actualType 'null' $actualType$propertyPath), 0$previous);
  183.         }
  184.     }
  185.     public function isReadable(object|array $objectOrArraystring|PropertyPathInterface $propertyPath): bool
  186.     {
  187.         if (!$propertyPath instanceof PropertyPathInterface) {
  188.             $propertyPath = new PropertyPath($propertyPath);
  189.         }
  190.         try {
  191.             $zval = [
  192.                 self::VALUE => $objectOrArray,
  193.             ];
  194.             $this->readPropertiesUntil($zval$propertyPath$propertyPath->getLength(), $this->ignoreInvalidIndices);
  195.             return true;
  196.         } catch (AccessException) {
  197.             return false;
  198.         } catch (UnexpectedTypeException) {
  199.             return false;
  200.         }
  201.     }
  202.     public function isWritable(object|array $objectOrArraystring|PropertyPathInterface $propertyPath): bool
  203.     {
  204.         $propertyPath $this->getPropertyPath($propertyPath);
  205.         try {
  206.             $zval = [
  207.                 self::VALUE => $objectOrArray,
  208.             ];
  209.             $propertyValues $this->readPropertiesUntil($zval$propertyPath$propertyPath->getLength() - 1);
  210.             for ($i \count($propertyValues) - 1<= $i; --$i) {
  211.                 $zval $propertyValues[$i];
  212.                 unset($propertyValues[$i]);
  213.                 if ($propertyPath->isIndex($i)) {
  214.                     if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
  215.                         return false;
  216.                     }
  217.                 } elseif (!\is_object($zval[self::VALUE]) || !$this->isPropertyWritable($zval[self::VALUE], $propertyPath->getElement($i))) {
  218.                     return false;
  219.                 }
  220.                 if (\is_object($zval[self::VALUE])) {
  221.                     return true;
  222.                 }
  223.             }
  224.             return true;
  225.         } catch (AccessException) {
  226.             return false;
  227.         } catch (UnexpectedTypeException) {
  228.             return false;
  229.         }
  230.     }
  231.     /**
  232.      * Reads the path from an object up to a given path index.
  233.      *
  234.      * @throws UnexpectedTypeException if a value within the path is neither object nor array
  235.      * @throws NoSuchIndexException    If a non-existing index is accessed
  236.      */
  237.     private function readPropertiesUntil(array $zvalPropertyPathInterface $propertyPathint $lastIndexbool $ignoreInvalidIndices true): array
  238.     {
  239.         if (!\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE])) {
  240.             throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath0);
  241.         }
  242.         // Add the root object to the list
  243.         $propertyValues = [$zval];
  244.         for ($i 0$i $lastIndex; ++$i) {
  245.             $property $propertyPath->getElement($i);
  246.             $isIndex $propertyPath->isIndex($i);
  247.             $isNullSafe false;
  248.             if (method_exists($propertyPath'isNullSafe')) {
  249.                 // To be removed in symfony 7 once we are sure isNullSafe is always implemented.
  250.                 $isNullSafe $propertyPath->isNullSafe($i);
  251.             } else {
  252.                 trigger_deprecation('symfony/property-access''6.2''The "%s()" method in class "%s" needs to be implemented in version 7.0, not defining it is deprecated.''isNullSafe'PropertyPathInterface::class);
  253.             }
  254.             if ($isIndex) {
  255.                 // Create missing nested arrays on demand
  256.                 if (($zval[self::VALUE] instanceof \ArrayAccess && !$zval[self::VALUE]->offsetExists($property)) ||
  257.                     (\is_array($zval[self::VALUE]) && !isset($zval[self::VALUE][$property]) && !\array_key_exists($property$zval[self::VALUE]))
  258.                 ) {
  259.                     if (!$ignoreInvalidIndices && !$isNullSafe) {
  260.                         if (!\is_array($zval[self::VALUE])) {
  261.                             if (!$zval[self::VALUE] instanceof \Traversable) {
  262.                                 throw new NoSuchIndexException(sprintf('Cannot read index "%s" while trying to traverse path "%s".'$property, (string) $propertyPath));
  263.                             }
  264.                             $zval[self::VALUE] = iterator_to_array($zval[self::VALUE]);
  265.                         }
  266.                         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)));
  267.                     }
  268.                     if ($i $propertyPath->getLength()) {
  269.                         if (isset($zval[self::REF])) {
  270.                             $zval[self::VALUE][$property] = [];
  271.                             $zval[self::REF] = $zval[self::VALUE];
  272.                         } else {
  273.                             $zval[self::VALUE] = [$property => []];
  274.                         }
  275.                     }
  276.                 }
  277.                 $zval $this->readIndex($zval$property);
  278.             } elseif ($isNullSafe && !\is_object($zval[self::VALUE])) {
  279.                 $zval[self::VALUE] = null;
  280.             } else {
  281.                 $zval $this->readProperty($zval$property$this->ignoreInvalidProperty$isNullSafe);
  282.             }
  283.             // the final value of the path must not be validated
  284.             if ($i $propertyPath->getLength() && !\is_object($zval[self::VALUE]) && !\is_array($zval[self::VALUE]) && !$isNullSafe) {
  285.                 throw new UnexpectedTypeException($zval[self::VALUE], $propertyPath$i 1);
  286.             }
  287.             if (isset($zval[self::REF]) && (=== $i || isset($propertyValues[$i 1][self::IS_REF_CHAINED]))) {
  288.                 // Set the IS_REF_CHAINED flag to true if:
  289.                 // current property is passed by reference and
  290.                 // it is the first element in the property path or
  291.                 // the IS_REF_CHAINED flag of its parent element is true
  292.                 // Basically, this flag is true only when the reference chain from the top element to current element is not broken
  293.                 $zval[self::IS_REF_CHAINED] = true;
  294.             }
  295.             $propertyValues[] = $zval;
  296.             if ($isNullSafe && null === $zval[self::VALUE]) {
  297.                 break;
  298.             }
  299.         }
  300.         return $propertyValues;
  301.     }
  302.     /**
  303.      * Reads a key from an array-like structure.
  304.      *
  305.      * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
  306.      */
  307.     private function readIndex(array $zvalstring|int $index): array
  308.     {
  309.         if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
  310.             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])));
  311.         }
  312.         $result self::RESULT_PROTO;
  313.         if (isset($zval[self::VALUE][$index])) {
  314.             $result[self::VALUE] = $zval[self::VALUE][$index];
  315.             if (!isset($zval[self::REF])) {
  316.                 // Save creating references when doing read-only lookups
  317.             } elseif (\is_array($zval[self::VALUE])) {
  318.                 $result[self::REF] = &$zval[self::REF][$index];
  319.             } elseif (\is_object($result[self::VALUE])) {
  320.                 $result[self::REF] = $result[self::VALUE];
  321.             }
  322.         }
  323.         return $result;
  324.     }
  325.     /**
  326.      * Reads the value of a property from an object.
  327.      *
  328.      * @throws NoSuchPropertyException If $ignoreInvalidProperty is false and the property does not exist or is not public
  329.      */
  330.     private function readProperty(array $zvalstring $propertybool $ignoreInvalidProperty falsebool $isNullSafe false): array
  331.     {
  332.         if (!\is_object($zval[self::VALUE])) {
  333.             throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%1$s]" instead.'$property));
  334.         }
  335.         $result self::RESULT_PROTO;
  336.         $object $zval[self::VALUE];
  337.         $class $object::class;
  338.         $access $this->getReadInfo($class$property);
  339.         if (null !== $access) {
  340.             $name $access->getName();
  341.             $type $access->getType();
  342.             try {
  343.                 if (PropertyReadInfo::TYPE_METHOD === $type) {
  344.                     try {
  345.                         $result[self::VALUE] = $object->$name();
  346.                     } catch (\TypeError $e) {
  347.                         [$trace] = $e->getTrace();
  348.                         // handle uninitialized properties in PHP >= 7
  349.                         if (__FILE__ === $trace['file']
  350.                             && $name === $trace['function']
  351.                             && $object instanceof $trace['class']
  352.                             && preg_match('/Return value (?:of .*::\w+\(\) )?must be of (?:the )?type (\w+), null returned$/'$e->getMessage(), $matches)
  353.                         ) {
  354.                             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);
  355.                         }
  356.                         throw $e;
  357.                     }
  358.                 } elseif (PropertyReadInfo::TYPE_PROPERTY === $type) {
  359.                     if ($access->canBeReference() && !isset($object->$name) && !\array_key_exists($name, (array) $object) && !(new \ReflectionProperty($class$name))->hasType()) {
  360.                         throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.'$class$name));
  361.                     }
  362.                     $result[self::VALUE] = $object->$name;
  363.                     if (isset($zval[self::REF]) && $access->canBeReference()) {
  364.                         $result[self::REF] = &$object->$name;
  365.                     }
  366.                 }
  367.             } catch (\Error $e) {
  368.                 // handle uninitialized properties in PHP >= 7.4
  369.                 if (preg_match('/^Typed property ([\w\\\\@]+)::\$(\w+) must not be accessed before initialization$/'$e->getMessage(), $matches)) {
  370.                     $r = new \ReflectionProperty(str_contains($matches[1], '@anonymous') ? $class $matches[1], $matches[2]);
  371.                     $type = ($type $r->getType()) instanceof \ReflectionNamedType $type->getName() : (string) $type;
  372.                     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);
  373.                 }
  374.                 throw $e;
  375.             }
  376.         } elseif (property_exists($object$property) && \array_key_exists($property, (array) $object)) {
  377.             $result[self::VALUE] = $object->$property;
  378.             if (isset($zval[self::REF])) {
  379.                 $result[self::REF] = &$object->$property;
  380.             }
  381.         } elseif ($isNullSafe) {
  382.             $result[self::VALUE] = null;
  383.         } elseif (!$ignoreInvalidProperty) {
  384.             throw new NoSuchPropertyException(sprintf('Can\'t get a way to read the property "%s" in class "%s".'$property$class));
  385.         }
  386.         // Objects are always passed around by reference
  387.         if (isset($zval[self::REF]) && \is_object($result[self::VALUE])) {
  388.             $result[self::REF] = $result[self::VALUE];
  389.         }
  390.         return $result;
  391.     }
  392.     /**
  393.      * Guesses how to read the property value.
  394.      */
  395.     private function getReadInfo(string $classstring $property): ?PropertyReadInfo
  396.     {
  397.         $key str_replace('\\''.'$class).'..'.$property;
  398.         if (isset($this->readPropertyCache[$key])) {
  399.             return $this->readPropertyCache[$key];
  400.         }
  401.         if ($this->cacheItemPool) {
  402.             $item $this->cacheItemPool->getItem(self::CACHE_PREFIX_READ.rawurlencode($key));
  403.             if ($item->isHit()) {
  404.                 return $this->readPropertyCache[$key] = $item->get();
  405.             }
  406.         }
  407.         $accessor $this->readInfoExtractor->getReadInfo($class$property, [
  408.             'enable_getter_setter_extraction' => true,
  409.             'enable_magic_methods_extraction' => $this->magicMethodsFlags,
  410.             'enable_constructor_extraction' => false,
  411.         ]);
  412.         if (isset($item)) {
  413.             $this->cacheItemPool->save($item->set($accessor));
  414.         }
  415.         return $this->readPropertyCache[$key] = $accessor;
  416.     }
  417.     /**
  418.      * Sets the value of an index in a given array-accessible value.
  419.      *
  420.      * @throws NoSuchIndexException If the array does not implement \ArrayAccess or it is not an array
  421.      */
  422.     private function writeIndex(array $zvalstring|int $indexmixed $value)
  423.     {
  424.         if (!$zval[self::VALUE] instanceof \ArrayAccess && !\is_array($zval[self::VALUE])) {
  425.             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])));
  426.         }
  427.         $zval[self::REF][$index] = $value;
  428.     }
  429.     /**
  430.      * Sets the value of a property in the given object.
  431.      *
  432.      * @throws NoSuchPropertyException if the property does not exist or is not public
  433.      */
  434.     private function writeProperty(array $zvalstring $propertymixed $value)
  435.     {
  436.         if (!\is_object($zval[self::VALUE])) {
  437.             throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?'$property));
  438.         }
  439.         $object $zval[self::VALUE];
  440.         $class $object::class;
  441.         $mutator $this->getWriteInfo($class$property$value);
  442.         if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
  443.             $type $mutator->getType();
  444.             if (PropertyWriteInfo::TYPE_METHOD === $type) {
  445.                 $object->{$mutator->getName()}($value);
  446.             } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
  447.                 $object->{$mutator->getName()} = $value;
  448.             } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
  449.                 $this->writeCollection($zval$property$value$mutator->getAdderInfo(), $mutator->getRemoverInfo());
  450.             }
  451.         } elseif ($object instanceof \stdClass && property_exists($object$property)) {
  452.             $object->$property $value;
  453.         } elseif (!$this->ignoreInvalidProperty) {
  454.             if ($mutator->hasErrors()) {
  455.                 throw new NoSuchPropertyException(implode('. '$mutator->getErrors()).'.');
  456.             }
  457.             throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".'$propertyget_debug_type($object)));
  458.         }
  459.     }
  460.     /**
  461.      * Adjusts a collection-valued property by calling add*() and remove*() methods.
  462.      */
  463.     private function writeCollection(array $zvalstring $propertyiterable $collectionPropertyWriteInfo $addMethodPropertyWriteInfo $removeMethod)
  464.     {
  465.         // At this point the add and remove methods have been found
  466.         $previousValue $this->readProperty($zval$property);
  467.         $previousValue $previousValue[self::VALUE];
  468.         $removeMethodName $removeMethod->getName();
  469.         $addMethodName $addMethod->getName();
  470.         if ($previousValue instanceof \Traversable) {
  471.             $previousValue iterator_to_array($previousValue);
  472.         }
  473.         if ($previousValue && \is_array($previousValue)) {
  474.             if (\is_object($collection)) {
  475.                 $collection iterator_to_array($collection);
  476.             }
  477.             foreach ($previousValue as $key => $item) {
  478.                 if (!\in_array($item$collectiontrue)) {
  479.                     unset($previousValue[$key]);
  480.                     $zval[self::VALUE]->$removeMethodName($item);
  481.                 }
  482.             }
  483.         } else {
  484.             $previousValue false;
  485.         }
  486.         foreach ($collection as $item) {
  487.             if (!$previousValue || !\in_array($item$previousValuetrue)) {
  488.                 $zval[self::VALUE]->$addMethodName($item);
  489.             }
  490.         }
  491.     }
  492.     private function getWriteInfo(string $classstring $propertymixed $value): PropertyWriteInfo
  493.     {
  494.         $useAdderAndRemover is_iterable($value);
  495.         $key str_replace('\\''.'$class).'..'.$property.'..'.(int) $useAdderAndRemover;
  496.         if (isset($this->writePropertyCache[$key])) {
  497.             return $this->writePropertyCache[$key];
  498.         }
  499.         if ($this->cacheItemPool) {
  500.             $item $this->cacheItemPool->getItem(self::CACHE_PREFIX_WRITE.rawurlencode($key));
  501.             if ($item->isHit()) {
  502.                 return $this->writePropertyCache[$key] = $item->get();
  503.             }
  504.         }
  505.         $mutator $this->writeInfoExtractor->getWriteInfo($class$property, [
  506.             'enable_getter_setter_extraction' => true,
  507.             'enable_magic_methods_extraction' => $this->magicMethodsFlags,
  508.             'enable_constructor_extraction' => false,
  509.             'enable_adder_remover_extraction' => $useAdderAndRemover,
  510.         ]);
  511.         if (isset($item)) {
  512.             $this->cacheItemPool->save($item->set($mutator));
  513.         }
  514.         return $this->writePropertyCache[$key] = $mutator;
  515.     }
  516.     /**
  517.      * Returns whether a property is writable in the given object.
  518.      */
  519.     private function isPropertyWritable(object $objectstring $property): bool
  520.     {
  521.         $mutatorForArray $this->getWriteInfo($object::class, $property, []);
  522.         if (PropertyWriteInfo::TYPE_NONE !== $mutatorForArray->getType() || ($object instanceof \stdClass && property_exists($object$property))) {
  523.             return true;
  524.         }
  525.         $mutator $this->getWriteInfo($object::class, $property'');
  526.         return PropertyWriteInfo::TYPE_NONE !== $mutator->getType() || ($object instanceof \stdClass && property_exists($object$property));
  527.     }
  528.     /**
  529.      * Gets a PropertyPath instance and caches it.
  530.      */
  531.     private function getPropertyPath(string|PropertyPath $propertyPath): PropertyPath
  532.     {
  533.         if ($propertyPath instanceof PropertyPathInterface) {
  534.             // Don't call the copy constructor has it is not needed here
  535.             return $propertyPath;
  536.         }
  537.         if (isset($this->propertyPathCache[$propertyPath])) {
  538.             return $this->propertyPathCache[$propertyPath];
  539.         }
  540.         if ($this->cacheItemPool) {
  541.             $item $this->cacheItemPool->getItem(self::CACHE_PREFIX_PROPERTY_PATH.rawurlencode($propertyPath));
  542.             if ($item->isHit()) {
  543.                 return $this->propertyPathCache[$propertyPath] = $item->get();
  544.             }
  545.         }
  546.         $propertyPathInstance = new PropertyPath($propertyPath);
  547.         if (isset($item)) {
  548.             $item->set($propertyPathInstance);
  549.             $this->cacheItemPool->save($item);
  550.         }
  551.         return $this->propertyPathCache[$propertyPath] = $propertyPathInstance;
  552.     }
  553.     /**
  554.      * Creates the APCu adapter if applicable.
  555.      *
  556.      * @throws \LogicException When the Cache Component isn't available
  557.      */
  558.     public static function createCache(string $namespaceint $defaultLifetimestring $versionLoggerInterface $logger null): AdapterInterface
  559.     {
  560.         if (!class_exists(ApcuAdapter::class)) {
  561.             throw new \LogicException(sprintf('The Symfony Cache component must be installed to use "%s()".'__METHOD__));
  562.         }
  563.         if (!ApcuAdapter::isSupported()) {
  564.             return new NullAdapter();
  565.         }
  566.         $apcu = new ApcuAdapter($namespace$defaultLifetime 5$version);
  567.         if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) {
  568.             $apcu->setLogger(new NullLogger());
  569.         } elseif (null !== $logger) {
  570.             $apcu->setLogger($logger);
  571.         }
  572.         return $apcu;
  573.     }
  574. }