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

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