vendor/symfony/property-info/Extractor/ReflectionExtractor.php line 86

  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\PropertyInfo\Extractor;
  11. use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
  12. use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
  13. use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
  14. use Symfony\Component\PropertyInfo\PropertyReadInfo;
  15. use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
  16. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  17. use Symfony\Component\PropertyInfo\PropertyWriteInfo;
  18. use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
  19. use Symfony\Component\PropertyInfo\Type;
  20. use Symfony\Component\String\Inflector\EnglishInflector;
  21. use Symfony\Component\String\Inflector\InflectorInterface;
  22. /**
  23.  * Extracts data using the reflection API.
  24.  *
  25.  * @author Kévin Dunglas <dunglas@gmail.com>
  26.  *
  27.  * @final
  28.  */
  29. class ReflectionExtractor implements PropertyListExtractorInterfacePropertyTypeExtractorInterfacePropertyAccessExtractorInterfacePropertyInitializableExtractorInterfacePropertyReadInfoExtractorInterfacePropertyWriteInfoExtractorInterfaceConstructorArgumentTypeExtractorInterface
  30. {
  31.     /**
  32.      * @internal
  33.      */
  34.     public static array $defaultMutatorPrefixes = ['add''remove''set'];
  35.     /**
  36.      * @internal
  37.      */
  38.     public static array $defaultAccessorPrefixes = ['get''is''has''can'];
  39.     /**
  40.      * @internal
  41.      */
  42.     public static array $defaultArrayMutatorPrefixes = ['add''remove'];
  43.     public const ALLOW_PRIVATE 1;
  44.     public const ALLOW_PROTECTED 2;
  45.     public const ALLOW_PUBLIC 4;
  46.     /** @var int Allow none of the magic methods */
  47.     public const DISALLOW_MAGIC_METHODS 0;
  48.     /** @var int Allow magic __get methods */
  49.     public const ALLOW_MAGIC_GET << 0;
  50.     /** @var int Allow magic __set methods */
  51.     public const ALLOW_MAGIC_SET << 1;
  52.     /** @var int Allow magic __call methods */
  53.     public const ALLOW_MAGIC_CALL << 2;
  54.     private const MAP_TYPES = [
  55.         'integer' => Type::BUILTIN_TYPE_INT,
  56.         'boolean' => Type::BUILTIN_TYPE_BOOL,
  57.         'double' => Type::BUILTIN_TYPE_FLOAT,
  58.     ];
  59.     private $mutatorPrefixes;
  60.     private $accessorPrefixes;
  61.     private $arrayMutatorPrefixes;
  62.     private $enableConstructorExtraction;
  63.     private $methodReflectionFlags;
  64.     private $magicMethodsFlags;
  65.     private $propertyReflectionFlags;
  66.     private $inflector;
  67.     private $arrayMutatorPrefixesFirst;
  68.     private $arrayMutatorPrefixesLast;
  69.     /**
  70.      * @param string[]|null $mutatorPrefixes
  71.      * @param string[]|null $accessorPrefixes
  72.      * @param string[]|null $arrayMutatorPrefixes
  73.      */
  74.     public function __construct(array $mutatorPrefixes null, array $accessorPrefixes null, array $arrayMutatorPrefixes nullbool $enableConstructorExtraction trueint $accessFlags self::ALLOW_PUBLICInflectorInterface $inflector nullint $magicMethodsFlags self::ALLOW_MAGIC_GET self::ALLOW_MAGIC_SET)
  75.     {
  76.         $this->mutatorPrefixes $mutatorPrefixes ?? self::$defaultMutatorPrefixes;
  77.         $this->accessorPrefixes $accessorPrefixes ?? self::$defaultAccessorPrefixes;
  78.         $this->arrayMutatorPrefixes $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes;
  79.         $this->enableConstructorExtraction $enableConstructorExtraction;
  80.         $this->methodReflectionFlags $this->getMethodsFlags($accessFlags);
  81.         $this->propertyReflectionFlags $this->getPropertyFlags($accessFlags);
  82.         $this->magicMethodsFlags $magicMethodsFlags;
  83.         $this->inflector $inflector ?? new EnglishInflector();
  84.         $this->arrayMutatorPrefixesFirst array_merge($this->arrayMutatorPrefixesarray_diff($this->mutatorPrefixes$this->arrayMutatorPrefixes));
  85.         $this->arrayMutatorPrefixesLast array_reverse($this->arrayMutatorPrefixesFirst);
  86.     }
  87.     public function getProperties(string $class, array $context = []): ?array
  88.     {
  89.         try {
  90.             $reflectionClass = new \ReflectionClass($class);
  91.         } catch (\ReflectionException) {
  92.             return null;
  93.         }
  94.         $reflectionProperties $reflectionClass->getProperties();
  95.         $properties = [];
  96.         foreach ($reflectionProperties as $reflectionProperty) {
  97.             if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) {
  98.                 $properties[$reflectionProperty->name] = $reflectionProperty->name;
  99.             }
  100.         }
  101.         foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) {
  102.             if ($reflectionMethod->isStatic()) {
  103.                 continue;
  104.             }
  105.             $propertyName $this->getPropertyName($reflectionMethod->name$reflectionProperties);
  106.             if (!$propertyName || isset($properties[$propertyName])) {
  107.                 continue;
  108.             }
  109.             if ($reflectionClass->hasProperty($lowerCasedPropertyName lcfirst($propertyName)) || (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/'$propertyName))) {
  110.                 $propertyName $lowerCasedPropertyName;
  111.             }
  112.             $properties[$propertyName] = $propertyName;
  113.         }
  114.         return $properties array_values($properties) : null;
  115.     }
  116.     public function getTypes(string $classstring $property, array $context = []): ?array
  117.     {
  118.         if ($fromMutator $this->extractFromMutator($class$property)) {
  119.             return $fromMutator;
  120.         }
  121.         if ($fromAccessor $this->extractFromAccessor($class$property)) {
  122.             return $fromAccessor;
  123.         }
  124.         if (
  125.             ($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) &&
  126.             $fromConstructor $this->extractFromConstructor($class$property)
  127.         ) {
  128.             return $fromConstructor;
  129.         }
  130.         if ($fromPropertyDeclaration $this->extractFromPropertyDeclaration($class$property)) {
  131.             return $fromPropertyDeclaration;
  132.         }
  133.         return null;
  134.     }
  135.     public function getTypesFromConstructor(string $classstring $property): ?array
  136.     {
  137.         try {
  138.             $reflection = new \ReflectionClass($class);
  139.         } catch (\ReflectionException) {
  140.             return null;
  141.         }
  142.         if (!$reflectionConstructor $reflection->getConstructor()) {
  143.             return null;
  144.         }
  145.         if (!$reflectionParameter $this->getReflectionParameterFromConstructor($property$reflectionConstructor)) {
  146.             return null;
  147.         }
  148.         if (!$reflectionType $reflectionParameter->getType()) {
  149.             return null;
  150.         }
  151.         if (!$types $this->extractFromReflectionType($reflectionType$reflectionConstructor->getDeclaringClass())) {
  152.             return null;
  153.         }
  154.         return $types;
  155.     }
  156.     private function getReflectionParameterFromConstructor(string $property\ReflectionMethod $reflectionConstructor): ?\ReflectionParameter
  157.     {
  158.         $reflectionParameter null;
  159.         foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
  160.             if ($reflectionParameter->getName() === $property) {
  161.                 return $reflectionParameter;
  162.             }
  163.         }
  164.         return null;
  165.     }
  166.     public function isReadable(string $classstring $property, array $context = []): ?bool
  167.     {
  168.         if ($this->isAllowedProperty($class$property)) {
  169.             return true;
  170.         }
  171.         return null !== $this->getReadInfo($class$property$context);
  172.     }
  173.     public function isWritable(string $classstring $property, array $context = []): ?bool
  174.     {
  175.         if ($this->isAllowedProperty($class$propertytrue)) {
  176.             return true;
  177.         }
  178.         [$reflectionMethod] = $this->getMutatorMethod($class$property);
  179.         return null !== $reflectionMethod;
  180.     }
  181.     public function isInitializable(string $classstring $property, array $context = []): ?bool
  182.     {
  183.         try {
  184.             $reflectionClass = new \ReflectionClass($class);
  185.         } catch (\ReflectionException) {
  186.             return null;
  187.         }
  188.         if (!$reflectionClass->isInstantiable()) {
  189.             return false;
  190.         }
  191.         if ($constructor $reflectionClass->getConstructor()) {
  192.             foreach ($constructor->getParameters() as $parameter) {
  193.                 if ($property === $parameter->name) {
  194.                     return true;
  195.                 }
  196.             }
  197.         } elseif ($parentClass $reflectionClass->getParentClass()) {
  198.             return $this->isInitializable($parentClass->getName(), $property);
  199.         }
  200.         return false;
  201.     }
  202.     public function getReadInfo(string $classstring $property, array $context = []): ?PropertyReadInfo
  203.     {
  204.         try {
  205.             $reflClass = new \ReflectionClass($class);
  206.         } catch (\ReflectionException) {
  207.             return null;
  208.         }
  209.         $allowGetterSetter $context['enable_getter_setter_extraction'] ?? false;
  210.         $magicMethods $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
  211.         $allowMagicCall = (bool) ($magicMethods self::ALLOW_MAGIC_CALL);
  212.         $allowMagicGet = (bool) ($magicMethods self::ALLOW_MAGIC_GET);
  213.         $hasProperty $reflClass->hasProperty($property);
  214.         $camelProp $this->camelize($property);
  215.         $getsetter lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
  216.         foreach ($this->accessorPrefixes as $prefix) {
  217.             $methodName $prefix.$camelProp;
  218.             if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) {
  219.                 $method $reflClass->getMethod($methodName);
  220.                 return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD$methodName$this->getReadVisiblityForMethod($method), $method->isStatic(), false);
  221.             }
  222.         }
  223.         if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) {
  224.             $method $reflClass->getMethod($getsetter);
  225.             return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD$getsetter$this->getReadVisiblityForMethod($method), $method->isStatic(), false);
  226.         }
  227.         if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
  228.             return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY$propertyPropertyReadInfo::VISIBILITY_PUBLICfalsefalse);
  229.         }
  230.         if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
  231.             $reflProperty $reflClass->getProperty($property);
  232.             return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY$property$this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
  233.         }
  234.         if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
  235.             return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD'get'.$camelPropPropertyReadInfo::VISIBILITY_PUBLICfalsefalse);
  236.         }
  237.         return null;
  238.     }
  239.     public function getWriteInfo(string $classstring $property, array $context = []): ?PropertyWriteInfo
  240.     {
  241.         try {
  242.             $reflClass = new \ReflectionClass($class);
  243.         } catch (\ReflectionException) {
  244.             return null;
  245.         }
  246.         $allowGetterSetter $context['enable_getter_setter_extraction'] ?? false;
  247.         $magicMethods $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
  248.         $allowMagicCall = (bool) ($magicMethods self::ALLOW_MAGIC_CALL);
  249.         $allowMagicSet = (bool) ($magicMethods self::ALLOW_MAGIC_SET);
  250.         $allowConstruct $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
  251.         $allowAdderRemover $context['enable_adder_remover_extraction'] ?? true;
  252.         $camelized $this->camelize($property);
  253.         $constructor $reflClass->getConstructor();
  254.         $singulars $this->inflector->singularize($camelized);
  255.         $errors = [];
  256.         if (null !== $constructor && $allowConstruct) {
  257.             foreach ($constructor->getParameters() as $parameter) {
  258.                 if ($parameter->getName() === $property) {
  259.                     return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR$property);
  260.                 }
  261.             }
  262.         }
  263.         [$adderAccessName$removerAccessName$adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass$singulars);
  264.         if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
  265.             $adderMethod $reflClass->getMethod($adderAccessName);
  266.             $removerMethod $reflClass->getMethod($removerAccessName);
  267.             $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
  268.             $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD$adderAccessName$this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()));
  269.             $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD$removerAccessName$this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic()));
  270.             return $mutator;
  271.         }
  272.         $errors[] = $adderAndRemoverErrors;
  273.         foreach ($this->mutatorPrefixes as $mutatorPrefix) {
  274.             $methodName $mutatorPrefix.$camelized;
  275.             [$accessible$methodAccessibleErrors] = $this->isMethodAccessible($reflClass$methodName1);
  276.             if (!$accessible) {
  277.                 $errors[] = $methodAccessibleErrors;
  278.                 continue;
  279.             }
  280.             $method $reflClass->getMethod($methodName);
  281.             if (!\in_array($mutatorPrefix$this->arrayMutatorPrefixestrue)) {
  282.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD$methodName$this->getWriteVisiblityForMethod($method), $method->isStatic());
  283.             }
  284.         }
  285.         $getsetter lcfirst($camelized);
  286.         if ($allowGetterSetter) {
  287.             [$accessible$methodAccessibleErrors] = $this->isMethodAccessible($reflClass$getsetter1);
  288.             if ($accessible) {
  289.                 $method $reflClass->getMethod($getsetter);
  290.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD$getsetter$this->getWriteVisiblityForMethod($method), $method->isStatic());
  291.             }
  292.             $errors[] = $methodAccessibleErrors;
  293.         }
  294.         if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
  295.             $reflProperty $reflClass->getProperty($property);
  296.             return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY$property$this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
  297.         }
  298.         if ($allowMagicSet) {
  299.             [$accessible$methodAccessibleErrors] = $this->isMethodAccessible($reflClass'__set'2);
  300.             if ($accessible) {
  301.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY$propertyPropertyWriteInfo::VISIBILITY_PUBLICfalse);
  302.             }
  303.             $errors[] = $methodAccessibleErrors;
  304.         }
  305.         if ($allowMagicCall) {
  306.             [$accessible$methodAccessibleErrors] = $this->isMethodAccessible($reflClass'__call'2);
  307.             if ($accessible) {
  308.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD'set'.$camelizedPropertyWriteInfo::VISIBILITY_PUBLICfalse);
  309.             }
  310.             $errors[] = $methodAccessibleErrors;
  311.         }
  312.         if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
  313.             $errors[] = [sprintf(
  314.                 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
  315.                 'the new value must be an array or an instance of \Traversable',
  316.                 $property,
  317.                 $reflClass->getName(),
  318.                 implode('()", "', [$adderAccessName$removerAccessName])
  319.             )];
  320.         }
  321.         $noneProperty = new PropertyWriteInfo();
  322.         $noneProperty->setErrors(array_merge([], ...$errors));
  323.         return $noneProperty;
  324.     }
  325.     /**
  326.      * @return Type[]|null
  327.      */
  328.     private function extractFromMutator(string $classstring $property): ?array
  329.     {
  330.         [$reflectionMethod$prefix] = $this->getMutatorMethod($class$property);
  331.         if (null === $reflectionMethod) {
  332.             return null;
  333.         }
  334.         $reflectionParameters $reflectionMethod->getParameters();
  335.         $reflectionParameter $reflectionParameters[0];
  336.         if (!$reflectionType $reflectionParameter->getType()) {
  337.             return null;
  338.         }
  339.         $type $this->extractFromReflectionType($reflectionType$reflectionMethod->getDeclaringClass());
  340.         if (=== \count($type) && \in_array($prefix$this->arrayMutatorPrefixes)) {
  341.             $type = [new Type(Type::BUILTIN_TYPE_ARRAYfalsenulltrue, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
  342.         }
  343.         return $type;
  344.     }
  345.     /**
  346.      * Tries to extract type information from accessors.
  347.      *
  348.      * @return Type[]|null
  349.      */
  350.     private function extractFromAccessor(string $classstring $property): ?array
  351.     {
  352.         [$reflectionMethod$prefix] = $this->getAccessorMethod($class$property);
  353.         if (null === $reflectionMethod) {
  354.             return null;
  355.         }
  356.         if ($reflectionType $reflectionMethod->getReturnType()) {
  357.             return $this->extractFromReflectionType($reflectionType$reflectionMethod->getDeclaringClass());
  358.         }
  359.         if (\in_array($prefix, ['is''can''has'])) {
  360.             return [new Type(Type::BUILTIN_TYPE_BOOL)];
  361.         }
  362.         return null;
  363.     }
  364.     /**
  365.      * Tries to extract type information from constructor.
  366.      *
  367.      * @return Type[]|null
  368.      */
  369.     private function extractFromConstructor(string $classstring $property): ?array
  370.     {
  371.         try {
  372.             $reflectionClass = new \ReflectionClass($class);
  373.         } catch (\ReflectionException) {
  374.             return null;
  375.         }
  376.         $constructor $reflectionClass->getConstructor();
  377.         if (!$constructor) {
  378.             return null;
  379.         }
  380.         foreach ($constructor->getParameters() as $parameter) {
  381.             if ($property !== $parameter->name) {
  382.                 continue;
  383.             }
  384.             $reflectionType $parameter->getType();
  385.             return $reflectionType $this->extractFromReflectionType($reflectionType$constructor->getDeclaringClass()) : null;
  386.         }
  387.         if ($parentClass $reflectionClass->getParentClass()) {
  388.             return $this->extractFromConstructor($parentClass->getName(), $property);
  389.         }
  390.         return null;
  391.     }
  392.     private function extractFromPropertyDeclaration(string $classstring $property): ?array
  393.     {
  394.         try {
  395.             $reflectionClass = new \ReflectionClass($class);
  396.             $reflectionProperty $reflectionClass->getProperty($property);
  397.             $reflectionPropertyType $reflectionProperty->getType();
  398.             if (null !== $reflectionPropertyType && $types $this->extractFromReflectionType($reflectionPropertyType$reflectionProperty->getDeclaringClass())) {
  399.                 return $types;
  400.             }
  401.         } catch (\ReflectionException) {
  402.             return null;
  403.         }
  404.         $defaultValue $reflectionClass->getDefaultProperties()[$property] ?? null;
  405.         if (null === $defaultValue) {
  406.             return null;
  407.         }
  408.         $type \gettype($defaultValue);
  409.         $type = static::MAP_TYPES[$type] ?? $type;
  410.         return [new Type($type$this->isNullableProperty($class$property), nullType::BUILTIN_TYPE_ARRAY === $type)];
  411.     }
  412.     private function extractFromReflectionType(\ReflectionType $reflectionType\ReflectionClass $declaringClass): array
  413.     {
  414.         $types = [];
  415.         $nullable $reflectionType->allowsNull();
  416.         foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) {
  417.             if (!$type instanceof \ReflectionNamedType) {
  418.                 // Nested composite types are not supported yet.
  419.                 return [];
  420.             }
  421.             $phpTypeOrClass $type->getName();
  422.             if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
  423.                 continue;
  424.             }
  425.             if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
  426.                 $types[] = new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue);
  427.             } elseif ('void' === $phpTypeOrClass) {
  428.                 $types[] = new Type(Type::BUILTIN_TYPE_NULL$nullable);
  429.             } elseif ($type->isBuiltin()) {
  430.                 $types[] = new Type($phpTypeOrClass$nullable);
  431.             } else {
  432.                 $types[] = new Type(Type::BUILTIN_TYPE_OBJECT$nullable$this->resolveTypeName($phpTypeOrClass$declaringClass));
  433.             }
  434.         }
  435.         return $types;
  436.     }
  437.     private function resolveTypeName(string $name\ReflectionClass $declaringClass): string
  438.     {
  439.         if ('self' === $lcName strtolower($name)) {
  440.             return $declaringClass->name;
  441.         }
  442.         if ('parent' === $lcName && $parent $declaringClass->getParentClass()) {
  443.             return $parent->name;
  444.         }
  445.         return $name;
  446.     }
  447.     private function isNullableProperty(string $classstring $property): bool
  448.     {
  449.         try {
  450.             $reflectionProperty = new \ReflectionProperty($class$property);
  451.             $reflectionPropertyType $reflectionProperty->getType();
  452.             return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
  453.         } catch (\ReflectionException) {
  454.             // Return false if the property doesn't exist
  455.         }
  456.         return false;
  457.     }
  458.     private function isAllowedProperty(string $classstring $propertybool $writeAccessRequired false): bool
  459.     {
  460.         try {
  461.             $reflectionProperty = new \ReflectionProperty($class$property);
  462.             if (\PHP_VERSION_ID >= 80100 && $writeAccessRequired && $reflectionProperty->isReadOnly()) {
  463.                 return false;
  464.             }
  465.             return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
  466.         } catch (\ReflectionException) {
  467.             // Return false if the property doesn't exist
  468.         }
  469.         return false;
  470.     }
  471.     /**
  472.      * Gets the accessor method.
  473.      *
  474.      * Returns an array with a the instance of \ReflectionMethod as first key
  475.      * and the prefix of the method as second or null if not found.
  476.      */
  477.     private function getAccessorMethod(string $classstring $property): ?array
  478.     {
  479.         $ucProperty ucfirst($property);
  480.         foreach ($this->accessorPrefixes as $prefix) {
  481.             try {
  482.                 $reflectionMethod = new \ReflectionMethod($class$prefix.$ucProperty);
  483.                 if ($reflectionMethod->isStatic()) {
  484.                     continue;
  485.                 }
  486.                 if (=== $reflectionMethod->getNumberOfRequiredParameters()) {
  487.                     return [$reflectionMethod$prefix];
  488.                 }
  489.             } catch (\ReflectionException) {
  490.                 // Return null if the property doesn't exist
  491.             }
  492.         }
  493.         return null;
  494.     }
  495.     /**
  496.      * Returns an array with a the instance of \ReflectionMethod as first key
  497.      * and the prefix of the method as second or null if not found.
  498.      */
  499.     private function getMutatorMethod(string $classstring $property): ?array
  500.     {
  501.         $ucProperty ucfirst($property);
  502.         $ucSingulars $this->inflector->singularize($ucProperty);
  503.         $mutatorPrefixes \in_array($ucProperty$ucSingularstrue) ? $this->arrayMutatorPrefixesLast $this->arrayMutatorPrefixesFirst;
  504.         foreach ($mutatorPrefixes as $prefix) {
  505.             $names = [$ucProperty];
  506.             if (\in_array($prefix$this->arrayMutatorPrefixes)) {
  507.                 $names array_merge($names$ucSingulars);
  508.             }
  509.             foreach ($names as $name) {
  510.                 try {
  511.                     $reflectionMethod = new \ReflectionMethod($class$prefix.$name);
  512.                     if ($reflectionMethod->isStatic()) {
  513.                         continue;
  514.                     }
  515.                     // Parameter can be optional to allow things like: method(array $foo = null)
  516.                     if ($reflectionMethod->getNumberOfParameters() >= 1) {
  517.                         return [$reflectionMethod$prefix];
  518.                     }
  519.                 } catch (\ReflectionException) {
  520.                     // Try the next prefix if the method doesn't exist
  521.                 }
  522.             }
  523.         }
  524.         return null;
  525.     }
  526.     private function getPropertyName(string $methodName, array $reflectionProperties): ?string
  527.     {
  528.         $pattern implode('|'array_merge($this->accessorPrefixes$this->mutatorPrefixes));
  529.         if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i'$methodName$matches)) {
  530.             if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
  531.                 return $matches[2];
  532.             }
  533.             foreach ($reflectionProperties as $reflectionProperty) {
  534.                 foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
  535.                     if (strtolower($name) === strtolower($matches[2])) {
  536.                         return $reflectionProperty->name;
  537.                     }
  538.                 }
  539.             }
  540.             return $matches[2];
  541.         }
  542.         return null;
  543.     }
  544.     /**
  545.      * Searches for add and remove methods.
  546.      *
  547.      * @param \ReflectionClass $reflClass The reflection class for the given object
  548.      * @param array            $singulars The singular form of the property name or null
  549.      *
  550.      * @return array An array containing the adder and remover when found and errors
  551.      */
  552.     private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
  553.     {
  554.         if (!\is_array($this->arrayMutatorPrefixes) && !== \count($this->arrayMutatorPrefixes)) {
  555.             return [nullnull, []];
  556.         }
  557.         [$addPrefix$removePrefix] = $this->arrayMutatorPrefixes;
  558.         $errors = [];
  559.         foreach ($singulars as $singular) {
  560.             $addMethod $addPrefix.$singular;
  561.             $removeMethod $removePrefix.$singular;
  562.             [$addMethodFound$addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass$addMethod1);
  563.             [$removeMethodFound$removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass$removeMethod1);
  564.             $errors[] = $addMethodAccessibleErrors;
  565.             $errors[] = $removeMethodAccessibleErrors;
  566.             if ($addMethodFound && $removeMethodFound) {
  567.                 return [$addMethod$removeMethod, []];
  568.             }
  569.             if ($addMethodFound && !$removeMethodFound) {
  570.                 $errors[] = [sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found'$addMethod$reflClass->getName(), $removeMethod)];
  571.             } elseif (!$addMethodFound && $removeMethodFound) {
  572.                 $errors[] = [sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found'$removeMethod$reflClass->getName(), $addMethod)];
  573.             }
  574.         }
  575.         return [nullnullarray_merge([], ...$errors)];
  576.     }
  577.     /**
  578.      * Returns whether a method is public and has the number of required parameters and errors.
  579.      */
  580.     private function isMethodAccessible(\ReflectionClass $classstring $methodNameint $parameters): array
  581.     {
  582.         $errors = [];
  583.         if ($class->hasMethod($methodName)) {
  584.             $method $class->getMethod($methodName);
  585.             if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
  586.                 $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.'$methodName$class->getName());
  587.             } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
  588.                 $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.'$methodName$class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
  589.             } else {
  590.                 return [true$errors];
  591.             }
  592.         }
  593.         return [false$errors];
  594.     }
  595.     /**
  596.      * Camelizes a given string.
  597.      */
  598.     private function camelize(string $string): string
  599.     {
  600.         return str_replace(' '''ucwords(str_replace('_'' '$string)));
  601.     }
  602.     /**
  603.      * Return allowed reflection method flags.
  604.      */
  605.     private function getMethodsFlags(int $accessFlags): int
  606.     {
  607.         $methodFlags 0;
  608.         if ($accessFlags self::ALLOW_PUBLIC) {
  609.             $methodFlags |= \ReflectionMethod::IS_PUBLIC;
  610.         }
  611.         if ($accessFlags self::ALLOW_PRIVATE) {
  612.             $methodFlags |= \ReflectionMethod::IS_PRIVATE;
  613.         }
  614.         if ($accessFlags self::ALLOW_PROTECTED) {
  615.             $methodFlags |= \ReflectionMethod::IS_PROTECTED;
  616.         }
  617.         return $methodFlags;
  618.     }
  619.     /**
  620.      * Return allowed reflection property flags.
  621.      */
  622.     private function getPropertyFlags(int $accessFlags): int
  623.     {
  624.         $propertyFlags 0;
  625.         if ($accessFlags self::ALLOW_PUBLIC) {
  626.             $propertyFlags |= \ReflectionProperty::IS_PUBLIC;
  627.         }
  628.         if ($accessFlags self::ALLOW_PRIVATE) {
  629.             $propertyFlags |= \ReflectionProperty::IS_PRIVATE;
  630.         }
  631.         if ($accessFlags self::ALLOW_PROTECTED) {
  632.             $propertyFlags |= \ReflectionProperty::IS_PROTECTED;
  633.         }
  634.         return $propertyFlags;
  635.     }
  636.     private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
  637.     {
  638.         if ($reflectionProperty->isPrivate()) {
  639.             return PropertyReadInfo::VISIBILITY_PRIVATE;
  640.         }
  641.         if ($reflectionProperty->isProtected()) {
  642.             return PropertyReadInfo::VISIBILITY_PROTECTED;
  643.         }
  644.         return PropertyReadInfo::VISIBILITY_PUBLIC;
  645.     }
  646.     private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
  647.     {
  648.         if ($reflectionMethod->isPrivate()) {
  649.             return PropertyReadInfo::VISIBILITY_PRIVATE;
  650.         }
  651.         if ($reflectionMethod->isProtected()) {
  652.             return PropertyReadInfo::VISIBILITY_PROTECTED;
  653.         }
  654.         return PropertyReadInfo::VISIBILITY_PUBLIC;
  655.     }
  656.     private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
  657.     {
  658.         if ($reflectionProperty->isPrivate()) {
  659.             return PropertyWriteInfo::VISIBILITY_PRIVATE;
  660.         }
  661.         if ($reflectionProperty->isProtected()) {
  662.             return PropertyWriteInfo::VISIBILITY_PROTECTED;
  663.         }
  664.         return PropertyWriteInfo::VISIBILITY_PUBLIC;
  665.     }
  666.     private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
  667.     {
  668.         if ($reflectionMethod->isPrivate()) {
  669.             return PropertyWriteInfo::VISIBILITY_PRIVATE;
  670.         }
  671.         if ($reflectionMethod->isProtected()) {
  672.             return PropertyWriteInfo::VISIBILITY_PROTECTED;
  673.         }
  674.         return PropertyWriteInfo::VISIBILITY_PUBLIC;
  675.     }
  676. }