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

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