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

  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.             if (\PHP_VERSION_ID 80100 || !$reflProperty->isReadOnly()) {
  297.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY$property$this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
  298.             }
  299.             $errors[] = [sprintf('The property "%s" in class "%s" is a promoted readonly property.'$property$reflClass->getName())];
  300.             $allowMagicSet $allowMagicCall false;
  301.         }
  302.         if ($allowMagicSet) {
  303.             [$accessible$methodAccessibleErrors] = $this->isMethodAccessible($reflClass'__set'2);
  304.             if ($accessible) {
  305.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY$propertyPropertyWriteInfo::VISIBILITY_PUBLICfalse);
  306.             }
  307.             $errors[] = $methodAccessibleErrors;
  308.         }
  309.         if ($allowMagicCall) {
  310.             [$accessible$methodAccessibleErrors] = $this->isMethodAccessible($reflClass'__call'2);
  311.             if ($accessible) {
  312.                 return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD'set'.$camelizedPropertyWriteInfo::VISIBILITY_PUBLICfalse);
  313.             }
  314.             $errors[] = $methodAccessibleErrors;
  315.         }
  316.         if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
  317.             $errors[] = [sprintf(
  318.                 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
  319.                 'the new value must be an array or an instance of \Traversable',
  320.                 $property,
  321.                 $reflClass->getName(),
  322.                 implode('()", "', [$adderAccessName$removerAccessName])
  323.             )];
  324.         }
  325.         $noneProperty = new PropertyWriteInfo();
  326.         $noneProperty->setErrors(array_merge([], ...$errors));
  327.         return $noneProperty;
  328.     }
  329.     /**
  330.      * @return Type[]|null
  331.      */
  332.     private function extractFromMutator(string $classstring $property): ?array
  333.     {
  334.         [$reflectionMethod$prefix] = $this->getMutatorMethod($class$property);
  335.         if (null === $reflectionMethod) {
  336.             return null;
  337.         }
  338.         $reflectionParameters $reflectionMethod->getParameters();
  339.         $reflectionParameter $reflectionParameters[0];
  340.         if (!$reflectionType $reflectionParameter->getType()) {
  341.             return null;
  342.         }
  343.         $type $this->extractFromReflectionType($reflectionType$reflectionMethod->getDeclaringClass());
  344.         if (=== \count($type) && \in_array($prefix$this->arrayMutatorPrefixes)) {
  345.             $type = [new Type(Type::BUILTIN_TYPE_ARRAYfalsenulltrue, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
  346.         }
  347.         return $type;
  348.     }
  349.     /**
  350.      * Tries to extract type information from accessors.
  351.      *
  352.      * @return Type[]|null
  353.      */
  354.     private function extractFromAccessor(string $classstring $property): ?array
  355.     {
  356.         [$reflectionMethod$prefix] = $this->getAccessorMethod($class$property);
  357.         if (null === $reflectionMethod) {
  358.             return null;
  359.         }
  360.         if ($reflectionType $reflectionMethod->getReturnType()) {
  361.             return $this->extractFromReflectionType($reflectionType$reflectionMethod->getDeclaringClass());
  362.         }
  363.         if (\in_array($prefix, ['is''can''has'])) {
  364.             return [new Type(Type::BUILTIN_TYPE_BOOL)];
  365.         }
  366.         return null;
  367.     }
  368.     /**
  369.      * Tries to extract type information from constructor.
  370.      *
  371.      * @return Type[]|null
  372.      */
  373.     private function extractFromConstructor(string $classstring $property): ?array
  374.     {
  375.         try {
  376.             $reflectionClass = new \ReflectionClass($class);
  377.         } catch (\ReflectionException) {
  378.             return null;
  379.         }
  380.         $constructor $reflectionClass->getConstructor();
  381.         if (!$constructor) {
  382.             return null;
  383.         }
  384.         foreach ($constructor->getParameters() as $parameter) {
  385.             if ($property !== $parameter->name) {
  386.                 continue;
  387.             }
  388.             $reflectionType $parameter->getType();
  389.             return $reflectionType $this->extractFromReflectionType($reflectionType$constructor->getDeclaringClass()) : null;
  390.         }
  391.         if ($parentClass $reflectionClass->getParentClass()) {
  392.             return $this->extractFromConstructor($parentClass->getName(), $property);
  393.         }
  394.         return null;
  395.     }
  396.     private function extractFromPropertyDeclaration(string $classstring $property): ?array
  397.     {
  398.         try {
  399.             $reflectionClass = new \ReflectionClass($class);
  400.             $reflectionProperty $reflectionClass->getProperty($property);
  401.             $reflectionPropertyType $reflectionProperty->getType();
  402.             if (null !== $reflectionPropertyType && $types $this->extractFromReflectionType($reflectionPropertyType$reflectionProperty->getDeclaringClass())) {
  403.                 return $types;
  404.             }
  405.         } catch (\ReflectionException) {
  406.             return null;
  407.         }
  408.         $defaultValue $reflectionClass->getDefaultProperties()[$property] ?? null;
  409.         if (null === $defaultValue) {
  410.             return null;
  411.         }
  412.         $type \gettype($defaultValue);
  413.         $type = static::MAP_TYPES[$type] ?? $type;
  414.         return [new Type($type$this->isNullableProperty($class$property), nullType::BUILTIN_TYPE_ARRAY === $type)];
  415.     }
  416.     private function extractFromReflectionType(\ReflectionType $reflectionType\ReflectionClass $declaringClass): array
  417.     {
  418.         $types = [];
  419.         $nullable $reflectionType->allowsNull();
  420.         foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) {
  421.             if (!$type instanceof \ReflectionNamedType) {
  422.                 // Nested composite types are not supported yet.
  423.                 return [];
  424.             }
  425.             $phpTypeOrClass $type->getName();
  426.             if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
  427.                 continue;
  428.             }
  429.             if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
  430.                 $types[] = new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue);
  431.             } elseif ('void' === $phpTypeOrClass) {
  432.                 $types[] = new Type(Type::BUILTIN_TYPE_NULL$nullable);
  433.             } elseif ($type->isBuiltin()) {
  434.                 $types[] = new Type($phpTypeOrClass$nullable);
  435.             } else {
  436.                 $types[] = new Type(Type::BUILTIN_TYPE_OBJECT$nullable$this->resolveTypeName($phpTypeOrClass$declaringClass));
  437.             }
  438.         }
  439.         return $types;
  440.     }
  441.     private function resolveTypeName(string $name\ReflectionClass $declaringClass): string
  442.     {
  443.         if ('self' === $lcName strtolower($name)) {
  444.             return $declaringClass->name;
  445.         }
  446.         if ('parent' === $lcName && $parent $declaringClass->getParentClass()) {
  447.             return $parent->name;
  448.         }
  449.         return $name;
  450.     }
  451.     private function isNullableProperty(string $classstring $property): bool
  452.     {
  453.         try {
  454.             $reflectionProperty = new \ReflectionProperty($class$property);
  455.             $reflectionPropertyType $reflectionProperty->getType();
  456.             return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
  457.         } catch (\ReflectionException) {
  458.             // Return false if the property doesn't exist
  459.         }
  460.         return false;
  461.     }
  462.     private function isAllowedProperty(string $classstring $propertybool $writeAccessRequired false): bool
  463.     {
  464.         try {
  465.             $reflectionProperty = new \ReflectionProperty($class$property);
  466.             if (\PHP_VERSION_ID >= 80100 && $writeAccessRequired && $reflectionProperty->isReadOnly()) {
  467.                 return false;
  468.             }
  469.             return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
  470.         } catch (\ReflectionException) {
  471.             // Return false if the property doesn't exist
  472.         }
  473.         return false;
  474.     }
  475.     /**
  476.      * Gets the accessor method.
  477.      *
  478.      * Returns an array with a the instance of \ReflectionMethod as first key
  479.      * and the prefix of the method as second or null if not found.
  480.      */
  481.     private function getAccessorMethod(string $classstring $property): ?array
  482.     {
  483.         $ucProperty ucfirst($property);
  484.         foreach ($this->accessorPrefixes as $prefix) {
  485.             try {
  486.                 $reflectionMethod = new \ReflectionMethod($class$prefix.$ucProperty);
  487.                 if ($reflectionMethod->isStatic()) {
  488.                     continue;
  489.                 }
  490.                 if (=== $reflectionMethod->getNumberOfRequiredParameters()) {
  491.                     return [$reflectionMethod$prefix];
  492.                 }
  493.             } catch (\ReflectionException) {
  494.                 // Return null if the property doesn't exist
  495.             }
  496.         }
  497.         return null;
  498.     }
  499.     /**
  500.      * Returns an array with a the instance of \ReflectionMethod as first key
  501.      * and the prefix of the method as second or null if not found.
  502.      */
  503.     private function getMutatorMethod(string $classstring $property): ?array
  504.     {
  505.         $ucProperty ucfirst($property);
  506.         $ucSingulars $this->inflector->singularize($ucProperty);
  507.         $mutatorPrefixes \in_array($ucProperty$ucSingularstrue) ? $this->arrayMutatorPrefixesLast $this->arrayMutatorPrefixesFirst;
  508.         foreach ($mutatorPrefixes as $prefix) {
  509.             $names = [$ucProperty];
  510.             if (\in_array($prefix$this->arrayMutatorPrefixes)) {
  511.                 $names array_merge($names$ucSingulars);
  512.             }
  513.             foreach ($names as $name) {
  514.                 try {
  515.                     $reflectionMethod = new \ReflectionMethod($class$prefix.$name);
  516.                     if ($reflectionMethod->isStatic()) {
  517.                         continue;
  518.                     }
  519.                     // Parameter can be optional to allow things like: method(array $foo = null)
  520.                     if ($reflectionMethod->getNumberOfParameters() >= 1) {
  521.                         return [$reflectionMethod$prefix];
  522.                     }
  523.                 } catch (\ReflectionException) {
  524.                     // Try the next prefix if the method doesn't exist
  525.                 }
  526.             }
  527.         }
  528.         return null;
  529.     }
  530.     private function getPropertyName(string $methodName, array $reflectionProperties): ?string
  531.     {
  532.         $pattern implode('|'array_merge($this->accessorPrefixes$this->mutatorPrefixes));
  533.         if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i'$methodName$matches)) {
  534.             if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
  535.                 return $matches[2];
  536.             }
  537.             foreach ($reflectionProperties as $reflectionProperty) {
  538.                 foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
  539.                     if (strtolower($name) === strtolower($matches[2])) {
  540.                         return $reflectionProperty->name;
  541.                     }
  542.                 }
  543.             }
  544.             return $matches[2];
  545.         }
  546.         return null;
  547.     }
  548.     /**
  549.      * Searches for add and remove methods.
  550.      *
  551.      * @param \ReflectionClass $reflClass The reflection class for the given object
  552.      * @param array            $singulars The singular form of the property name or null
  553.      *
  554.      * @return array An array containing the adder and remover when found and errors
  555.      */
  556.     private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
  557.     {
  558.         if (!\is_array($this->arrayMutatorPrefixes) && !== \count($this->arrayMutatorPrefixes)) {
  559.             return [nullnull, []];
  560.         }
  561.         [$addPrefix$removePrefix] = $this->arrayMutatorPrefixes;
  562.         $errors = [];
  563.         foreach ($singulars as $singular) {
  564.             $addMethod $addPrefix.$singular;
  565.             $removeMethod $removePrefix.$singular;
  566.             [$addMethodFound$addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass$addMethod1);
  567.             [$removeMethodFound$removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass$removeMethod1);
  568.             $errors[] = $addMethodAccessibleErrors;
  569.             $errors[] = $removeMethodAccessibleErrors;
  570.             if ($addMethodFound && $removeMethodFound) {
  571.                 return [$addMethod$removeMethod, []];
  572.             }
  573.             if ($addMethodFound && !$removeMethodFound) {
  574.                 $errors[] = [sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found'$addMethod$reflClass->getName(), $removeMethod)];
  575.             } elseif (!$addMethodFound && $removeMethodFound) {
  576.                 $errors[] = [sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found'$removeMethod$reflClass->getName(), $addMethod)];
  577.             }
  578.         }
  579.         return [nullnullarray_merge([], ...$errors)];
  580.     }
  581.     /**
  582.      * Returns whether a method is public and has the number of required parameters and errors.
  583.      */
  584.     private function isMethodAccessible(\ReflectionClass $classstring $methodNameint $parameters): array
  585.     {
  586.         $errors = [];
  587.         if ($class->hasMethod($methodName)) {
  588.             $method $class->getMethod($methodName);
  589.             if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
  590.                 $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.'$methodName$class->getName());
  591.             } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
  592.                 $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.'$methodName$class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
  593.             } else {
  594.                 return [true$errors];
  595.             }
  596.         }
  597.         return [false$errors];
  598.     }
  599.     /**
  600.      * Camelizes a given string.
  601.      */
  602.     private function camelize(string $string): string
  603.     {
  604.         return str_replace(' '''ucwords(str_replace('_'' '$string)));
  605.     }
  606.     /**
  607.      * Return allowed reflection method flags.
  608.      */
  609.     private function getMethodsFlags(int $accessFlags): int
  610.     {
  611.         $methodFlags 0;
  612.         if ($accessFlags self::ALLOW_PUBLIC) {
  613.             $methodFlags |= \ReflectionMethod::IS_PUBLIC;
  614.         }
  615.         if ($accessFlags self::ALLOW_PRIVATE) {
  616.             $methodFlags |= \ReflectionMethod::IS_PRIVATE;
  617.         }
  618.         if ($accessFlags self::ALLOW_PROTECTED) {
  619.             $methodFlags |= \ReflectionMethod::IS_PROTECTED;
  620.         }
  621.         return $methodFlags;
  622.     }
  623.     /**
  624.      * Return allowed reflection property flags.
  625.      */
  626.     private function getPropertyFlags(int $accessFlags): int
  627.     {
  628.         $propertyFlags 0;
  629.         if ($accessFlags self::ALLOW_PUBLIC) {
  630.             $propertyFlags |= \ReflectionProperty::IS_PUBLIC;
  631.         }
  632.         if ($accessFlags self::ALLOW_PRIVATE) {
  633.             $propertyFlags |= \ReflectionProperty::IS_PRIVATE;
  634.         }
  635.         if ($accessFlags self::ALLOW_PROTECTED) {
  636.             $propertyFlags |= \ReflectionProperty::IS_PROTECTED;
  637.         }
  638.         return $propertyFlags;
  639.     }
  640.     private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
  641.     {
  642.         if ($reflectionProperty->isPrivate()) {
  643.             return PropertyReadInfo::VISIBILITY_PRIVATE;
  644.         }
  645.         if ($reflectionProperty->isProtected()) {
  646.             return PropertyReadInfo::VISIBILITY_PROTECTED;
  647.         }
  648.         return PropertyReadInfo::VISIBILITY_PUBLIC;
  649.     }
  650.     private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
  651.     {
  652.         if ($reflectionMethod->isPrivate()) {
  653.             return PropertyReadInfo::VISIBILITY_PRIVATE;
  654.         }
  655.         if ($reflectionMethod->isProtected()) {
  656.             return PropertyReadInfo::VISIBILITY_PROTECTED;
  657.         }
  658.         return PropertyReadInfo::VISIBILITY_PUBLIC;
  659.     }
  660.     private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
  661.     {
  662.         if ($reflectionProperty->isPrivate()) {
  663.             return PropertyWriteInfo::VISIBILITY_PRIVATE;
  664.         }
  665.         if ($reflectionProperty->isProtected()) {
  666.             return PropertyWriteInfo::VISIBILITY_PROTECTED;
  667.         }
  668.         return PropertyWriteInfo::VISIBILITY_PUBLIC;
  669.     }
  670.     private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
  671.     {
  672.         if ($reflectionMethod->isPrivate()) {
  673.             return PropertyWriteInfo::VISIBILITY_PRIVATE;
  674.         }
  675.         if ($reflectionMethod->isProtected()) {
  676.             return PropertyWriteInfo::VISIBILITY_PROTECTED;
  677.         }
  678.         return PropertyWriteInfo::VISIBILITY_PUBLIC;
  679.     }
  680. }