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

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\PropertyInfo\Extractor;
  11. use Symfony\Component\Inflector\Inflector;
  12. use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
  13. use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
  14. use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
  15. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  16. use Symfony\Component\PropertyInfo\Type;
  17. /**
  18.  * Extracts data using the reflection API.
  19.  *
  20.  * @author Kévin Dunglas <dunglas@gmail.com>
  21.  *
  22.  * @final
  23.  */
  24. class ReflectionExtractor implements PropertyListExtractorInterfacePropertyTypeExtractorInterfacePropertyAccessExtractorInterfacePropertyInitializableExtractorInterface
  25. {
  26.     /**
  27.      * @internal
  28.      */
  29.     public static $defaultMutatorPrefixes = ['add''remove''set'];
  30.     /**
  31.      * @internal
  32.      */
  33.     public static $defaultAccessorPrefixes = ['is''can''get''has'];
  34.     /**
  35.      * @internal
  36.      */
  37.     public static $defaultArrayMutatorPrefixes = ['add''remove'];
  38.     public const ALLOW_PRIVATE 1;
  39.     public const ALLOW_PROTECTED 2;
  40.     public const ALLOW_PUBLIC 4;
  41.     private const MAP_TYPES = [
  42.         'integer' => Type::BUILTIN_TYPE_INT,
  43.         'boolean' => Type::BUILTIN_TYPE_BOOL,
  44.         'double' => Type::BUILTIN_TYPE_FLOAT,
  45.     ];
  46.     private $mutatorPrefixes;
  47.     private $accessorPrefixes;
  48.     private $arrayMutatorPrefixes;
  49.     private $enableConstructorExtraction;
  50.     private $accessFlags;
  51.     private $arrayMutatorPrefixesFirst;
  52.     private $arrayMutatorPrefixesLast;
  53.     /**
  54.      * @param string[]|null $mutatorPrefixes
  55.      * @param string[]|null $accessorPrefixes
  56.      * @param string[]|null $arrayMutatorPrefixes
  57.      */
  58.     public function __construct(array $mutatorPrefixes null, array $accessorPrefixes null, array $arrayMutatorPrefixes nullbool $enableConstructorExtraction trueint $accessFlags self::ALLOW_PUBLIC)
  59.     {
  60.         $this->mutatorPrefixes $mutatorPrefixes ?? self::$defaultMutatorPrefixes;
  61.         $this->accessorPrefixes $accessorPrefixes ?? self::$defaultAccessorPrefixes;
  62.         $this->arrayMutatorPrefixes $arrayMutatorPrefixes ?? self::$defaultArrayMutatorPrefixes;
  63.         $this->enableConstructorExtraction $enableConstructorExtraction;
  64.         $this->accessFlags $accessFlags;
  65.         $this->arrayMutatorPrefixesFirst array_merge($this->arrayMutatorPrefixesarray_diff($this->mutatorPrefixes$this->arrayMutatorPrefixes));
  66.         $this->arrayMutatorPrefixesLast array_reverse($this->arrayMutatorPrefixesFirst);
  67.     }
  68.     /**
  69.      * {@inheritdoc}
  70.      */
  71.     public function getProperties($class, array $context = []): ?array
  72.     {
  73.         try {
  74.             $reflectionClass = new \ReflectionClass($class);
  75.         } catch (\ReflectionException $e) {
  76.             return null;
  77.         }
  78.         $propertyFlags 0;
  79.         $methodFlags 0;
  80.         if ($this->accessFlags self::ALLOW_PUBLIC) {
  81.             $propertyFlags $propertyFlags | \ReflectionProperty::IS_PUBLIC;
  82.             $methodFlags $methodFlags | \ReflectionMethod::IS_PUBLIC;
  83.         }
  84.         if ($this->accessFlags self::ALLOW_PRIVATE) {
  85.             $propertyFlags $propertyFlags | \ReflectionProperty::IS_PRIVATE;
  86.             $methodFlags $methodFlags | \ReflectionMethod::IS_PRIVATE;
  87.         }
  88.         if ($this->accessFlags self::ALLOW_PROTECTED) {
  89.             $propertyFlags $propertyFlags | \ReflectionProperty::IS_PROTECTED;
  90.             $methodFlags $methodFlags | \ReflectionMethod::IS_PROTECTED;
  91.         }
  92.         $reflectionProperties $reflectionClass->getProperties();
  93.         $properties = [];
  94.         foreach ($reflectionProperties as $reflectionProperty) {
  95.             if ($reflectionProperty->getModifiers() & $propertyFlags) {
  96.                 $properties[$reflectionProperty->name] = $reflectionProperty->name;
  97.             }
  98.         }
  99.         foreach ($reflectionClass->getMethods($methodFlags) as $reflectionMethod) {
  100.             if ($reflectionMethod->isStatic()) {
  101.                 continue;
  102.             }
  103.             $propertyName $this->getPropertyName($reflectionMethod->name$reflectionProperties);
  104.             if (!$propertyName || isset($properties[$propertyName])) {
  105.                 continue;
  106.             }
  107.             if ($reflectionClass->hasProperty($lowerCasedPropertyName lcfirst($propertyName)) || (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/'$propertyName))) {
  108.                 $propertyName $lowerCasedPropertyName;
  109.             }
  110.             $properties[$propertyName] = $propertyName;
  111.         }
  112.         return $properties array_values($properties) : null;
  113.     }
  114.     /**
  115.      * {@inheritdoc}
  116.      */
  117.     public function getTypes($class$property, array $context = []): ?array
  118.     {
  119.         if ($fromMutator $this->extractFromMutator($class$property)) {
  120.             return $fromMutator;
  121.         }
  122.         if ($fromAccessor $this->extractFromAccessor($class$property)) {
  123.             return $fromAccessor;
  124.         }
  125.         if (
  126.             ($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) &&
  127.             $fromConstructor $this->extractFromConstructor($class$property)
  128.         ) {
  129.             return $fromConstructor;
  130.         }
  131.         if ($fromPropertyDeclaration $this->extractFromPropertyDeclaration($class$property)) {
  132.             return $fromPropertyDeclaration;
  133.         }
  134.         return null;
  135.     }
  136.     /**
  137.      * {@inheritdoc}
  138.      */
  139.     public function isReadable($class$property, array $context = []): ?bool
  140.     {
  141.         if ($this->isAllowedProperty($class$property)) {
  142.             return true;
  143.         }
  144.         [$reflectionMethod] = $this->getAccessorMethod($class$property);
  145.         return null !== $reflectionMethod;
  146.     }
  147.     /**
  148.      * {@inheritdoc}
  149.      */
  150.     public function isWritable($class$property, array $context = []): ?bool
  151.     {
  152.         if ($this->isAllowedProperty($class$property)) {
  153.             return true;
  154.         }
  155.         [$reflectionMethod] = $this->getMutatorMethod($class$property);
  156.         return null !== $reflectionMethod;
  157.     }
  158.     /**
  159.      * {@inheritdoc}
  160.      */
  161.     public function isInitializable(string $classstring $property, array $context = []): ?bool
  162.     {
  163.         try {
  164.             $reflectionClass = new \ReflectionClass($class);
  165.         } catch (\ReflectionException $e) {
  166.             return null;
  167.         }
  168.         if (!$reflectionClass->isInstantiable()) {
  169.             return false;
  170.         }
  171.         if ($constructor $reflectionClass->getConstructor()) {
  172.             foreach ($constructor->getParameters() as $parameter) {
  173.                 if ($property === $parameter->name) {
  174.                     return true;
  175.                 }
  176.             }
  177.         } elseif ($parentClass $reflectionClass->getParentClass()) {
  178.             return $this->isInitializable($parentClass->getName(), $property);
  179.         }
  180.         return false;
  181.     }
  182.     /**
  183.      * @return Type[]|null
  184.      */
  185.     private function extractFromMutator(string $classstring $property): ?array
  186.     {
  187.         [$reflectionMethod$prefix] = $this->getMutatorMethod($class$property);
  188.         if (null === $reflectionMethod) {
  189.             return null;
  190.         }
  191.         $reflectionParameters $reflectionMethod->getParameters();
  192.         $reflectionParameter $reflectionParameters[0];
  193.         if (!$reflectionType $reflectionParameter->getType()) {
  194.             return null;
  195.         }
  196.         $type $this->extractFromReflectionType($reflectionType$reflectionMethod->getDeclaringClass());
  197.         if (=== \count($type) && \in_array($prefix$this->arrayMutatorPrefixes)) {
  198.             $type = [new Type(Type::BUILTIN_TYPE_ARRAYfalsenulltrue, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
  199.         }
  200.         return $type;
  201.     }
  202.     /**
  203.      * Tries to extract type information from accessors.
  204.      *
  205.      * @return Type[]|null
  206.      */
  207.     private function extractFromAccessor(string $classstring $property): ?array
  208.     {
  209.         [$reflectionMethod$prefix] = $this->getAccessorMethod($class$property);
  210.         if (null === $reflectionMethod) {
  211.             return null;
  212.         }
  213.         if ($reflectionType $reflectionMethod->getReturnType()) {
  214.             return $this->extractFromReflectionType($reflectionType$reflectionMethod->getDeclaringClass());
  215.         }
  216.         if (\in_array($prefix, ['is''can''has'])) {
  217.             return [new Type(Type::BUILTIN_TYPE_BOOL)];
  218.         }
  219.         return null;
  220.     }
  221.     /**
  222.      * Tries to extract type information from constructor.
  223.      *
  224.      * @return Type[]|null
  225.      */
  226.     private function extractFromConstructor(string $classstring $property): ?array
  227.     {
  228.         try {
  229.             $reflectionClass = new \ReflectionClass($class);
  230.         } catch (\ReflectionException $e) {
  231.             return null;
  232.         }
  233.         $constructor $reflectionClass->getConstructor();
  234.         if (!$constructor) {
  235.             return null;
  236.         }
  237.         foreach ($constructor->getParameters() as $parameter) {
  238.             if ($property !== $parameter->name) {
  239.                 continue;
  240.             }
  241.             $reflectionType $parameter->getType();
  242.             return $reflectionType $this->extractFromReflectionType($reflectionType$constructor->getDeclaringClass()) : null;
  243.         }
  244.         if ($parentClass $reflectionClass->getParentClass()) {
  245.             return $this->extractFromConstructor($parentClass->getName(), $property);
  246.         }
  247.         return null;
  248.     }
  249.     private function extractFromPropertyDeclaration(string $classstring $property): ?array
  250.     {
  251.         try {
  252.             $reflectionClass = new \ReflectionClass($class);
  253.             if (\PHP_VERSION_ID >= 70400) {
  254.                 $reflectionProperty $reflectionClass->getProperty($property);
  255.                 $reflectionPropertyType $reflectionProperty->getType();
  256.                 if (null !== $reflectionPropertyType && $types $this->extractFromReflectionType($reflectionPropertyType$reflectionProperty->getDeclaringClass())) {
  257.                     return $types;
  258.                 }
  259.             }
  260.         } catch (\ReflectionException $e) {
  261.             return null;
  262.         }
  263.         $defaultValue $reflectionClass->getDefaultProperties()[$property] ?? null;
  264.         if (null === $defaultValue) {
  265.             return null;
  266.         }
  267.         $type = \gettype($defaultValue);
  268.         return [new Type(static::MAP_TYPES[$type] ?? $type$this->isNullableProperty($class$property))];
  269.     }
  270.     private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
  271.     {
  272.         $types = [];
  273.         $nullable $reflectionType->allowsNull();
  274.         foreach (($reflectionType instanceof \ReflectionUnionType || $reflectionType instanceof \ReflectionIntersectionType) ? $reflectionType->getTypes() : [$reflectionType] as $type) {
  275.             $phpTypeOrClass $type->getName();
  276.             if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass || 'never' === $phpTypeOrClass) {
  277.                 continue;
  278.             }
  279.             if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
  280.                 $types[] = new Type(Type::BUILTIN_TYPE_ARRAY$nullablenulltrue);
  281.             } elseif ('void' === $phpTypeOrClass) {
  282.                 $types[] = new Type(Type::BUILTIN_TYPE_NULL$nullable);
  283.             } elseif ($type->isBuiltin()) {
  284.                 $types[] = new Type($phpTypeOrClass$nullable);
  285.             } else {
  286.                 $types[] = new Type(Type::BUILTIN_TYPE_OBJECT$nullable$this->resolveTypeName($phpTypeOrClass$declaringClass));
  287.             }
  288.         }
  289.         return $types;
  290.     }
  291.     private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string
  292.     {
  293.         if ('self' === $lcName strtolower($name)) {
  294.             return $declaringClass->name;
  295.         }
  296.         if ('parent' === $lcName && $parent $declaringClass->getParentClass()) {
  297.             return $parent->name;
  298.         }
  299.         return $name;
  300.     }
  301.     private function isNullableProperty(string $classstring $property): bool
  302.     {
  303.         try {
  304.             $reflectionProperty = new \ReflectionProperty($class$property);
  305.             if (\PHP_VERSION_ID >= 70400) {
  306.                 $reflectionPropertyType $reflectionProperty->getType();
  307.                 return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
  308.             }
  309.             return false;
  310.         } catch (\ReflectionException $e) {
  311.             // Return false if the property doesn't exist
  312.         }
  313.         return false;
  314.     }
  315.     private function isAllowedProperty(string $classstring $property): bool
  316.     {
  317.         try {
  318.             $reflectionProperty = new \ReflectionProperty($class$property);
  319.             if ($this->accessFlags self::ALLOW_PUBLIC && $reflectionProperty->isPublic()) {
  320.                 return true;
  321.             }
  322.             if ($this->accessFlags self::ALLOW_PROTECTED && $reflectionProperty->isProtected()) {
  323.                 return true;
  324.             }
  325.             if ($this->accessFlags self::ALLOW_PRIVATE && $reflectionProperty->isPrivate()) {
  326.                 return true;
  327.             }
  328.             return false;
  329.         } catch (\ReflectionException $e) {
  330.             // Return false if the property doesn't exist
  331.         }
  332.         return false;
  333.     }
  334.     /**
  335.      * Gets the accessor method.
  336.      *
  337.      * Returns an array with a the instance of \ReflectionMethod as first key
  338.      * and the prefix of the method as second or null if not found.
  339.      */
  340.     private function getAccessorMethod(string $classstring $property): ?array
  341.     {
  342.         $ucProperty ucfirst($property);
  343.         foreach ($this->accessorPrefixes as $prefix) {
  344.             try {
  345.                 $reflectionMethod = new \ReflectionMethod($class$prefix.$ucProperty);
  346.                 if ($reflectionMethod->isStatic()) {
  347.                     continue;
  348.                 }
  349.                 if (=== $reflectionMethod->getNumberOfRequiredParameters()) {
  350.                     return [$reflectionMethod$prefix];
  351.                 }
  352.             } catch (\ReflectionException $e) {
  353.                 // Return null if the property doesn't exist
  354.             }
  355.         }
  356.         return null;
  357.     }
  358.     /**
  359.      * Returns an array with a the instance of \ReflectionMethod as first key
  360.      * and the prefix of the method as second or null if not found.
  361.      */
  362.     private function getMutatorMethod(string $classstring $property): ?array
  363.     {
  364.         $ucProperty ucfirst($property);
  365.         $ucSingulars = (array) Inflector::singularize($ucProperty);
  366.         $mutatorPrefixes = \in_array($ucProperty$ucSingularstrue) ? $this->arrayMutatorPrefixesLast $this->arrayMutatorPrefixesFirst;
  367.         foreach ($mutatorPrefixes as $prefix) {
  368.             $names = [$ucProperty];
  369.             if (\in_array($prefix$this->arrayMutatorPrefixes)) {
  370.                 $names array_merge($names$ucSingulars);
  371.             }
  372.             foreach ($names as $name) {
  373.                 try {
  374.                     $reflectionMethod = new \ReflectionMethod($class$prefix.$name);
  375.                     if ($reflectionMethod->isStatic()) {
  376.                         continue;
  377.                     }
  378.                     // Parameter can be optional to allow things like: method(array $foo = null)
  379.                     if ($reflectionMethod->getNumberOfParameters() >= 1) {
  380.                         return [$reflectionMethod$prefix];
  381.                     }
  382.                 } catch (\ReflectionException $e) {
  383.                     // Try the next prefix if the method doesn't exist
  384.                 }
  385.             }
  386.         }
  387.         return null;
  388.     }
  389.     private function getPropertyName(string $methodName, array $reflectionProperties): ?string
  390.     {
  391.         $pattern implode('|'array_merge($this->accessorPrefixes$this->mutatorPrefixes));
  392.         if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i'$methodName$matches)) {
  393.             if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
  394.                 return $matches[2];
  395.             }
  396.             foreach ($reflectionProperties as $reflectionProperty) {
  397.                 foreach ((array) Inflector::singularize($reflectionProperty->name) as $name) {
  398.                     if (strtolower($name) === strtolower($matches[2])) {
  399.                         return $reflectionProperty->name;
  400.                     }
  401.                 }
  402.             }
  403.             return $matches[2];
  404.         }
  405.         return null;
  406.     }
  407. }