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

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