123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\PropertyInfo\Extractor;
- use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
- use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
- use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
- use Symfony\Component\PropertyInfo\PropertyReadInfo;
- use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
- use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
- use Symfony\Component\PropertyInfo\PropertyWriteInfo;
- use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
- use Symfony\Component\PropertyInfo\Type;
- use Symfony\Component\String\Inflector\EnglishInflector;
- use Symfony\Component\String\Inflector\InflectorInterface;
- /**
- * Extracts data using the reflection API.
- *
- * @author Kévin Dunglas <dunglas@gmail.com>
- *
- * @final
- */
- class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, PropertyReadInfoExtractorInterface, PropertyWriteInfoExtractorInterface, ConstructorArgumentTypeExtractorInterface
- {
- /**
- * @internal
- */
- public static $defaultMutatorPrefixes = ['add', 'remove', 'set'];
- /**
- * @internal
- */
- public static $defaultAccessorPrefixes = ['get', 'is', 'has', 'can'];
- /**
- * @internal
- */
- public static $defaultArrayMutatorPrefixes = ['add', 'remove'];
- public const ALLOW_PRIVATE = 1;
- public const ALLOW_PROTECTED = 2;
- public const ALLOW_PUBLIC = 4;
- /** @var int Allow none of the magic methods */
- public const DISALLOW_MAGIC_METHODS = 0;
- /** @var int Allow magic __get methods */
- public const ALLOW_MAGIC_GET = 1 << 0;
- /** @var int Allow magic __set methods */
- public const ALLOW_MAGIC_SET = 1 << 1;
- /** @var int Allow magic __call methods */
- public const ALLOW_MAGIC_CALL = 1 << 2;
- private const MAP_TYPES = [
- 'integer' => Type::BUILTIN_TYPE_INT,
- 'boolean' => Type::BUILTIN_TYPE_BOOL,
- 'double' => Type::BUILTIN_TYPE_FLOAT,
- ];
- private $mutatorPrefixes;
- private $accessorPrefixes;
- private $arrayMutatorPrefixes;
- private $enableConstructorExtraction;
- private $methodReflectionFlags;
- private $magicMethodsFlags;
- private $propertyReflectionFlags;
- private $inflector;
- private $arrayMutatorPrefixesFirst;
- private $arrayMutatorPrefixesLast;
- /**
- * @param string[]|null $mutatorPrefixes
- * @param string[]|null $accessorPrefixes
- * @param string[]|null $arrayMutatorPrefixes
- */
- public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, InflectorInterface $inflector = null, int $magicMethodsFlags = self::ALLOW_MAGIC_GET | self::ALLOW_MAGIC_SET)
- {
- $this->mutatorPrefixes = null !== $mutatorPrefixes ? $mutatorPrefixes : self::$defaultMutatorPrefixes;
- $this->accessorPrefixes = null !== $accessorPrefixes ? $accessorPrefixes : self::$defaultAccessorPrefixes;
- $this->arrayMutatorPrefixes = null !== $arrayMutatorPrefixes ? $arrayMutatorPrefixes : self::$defaultArrayMutatorPrefixes;
- $this->enableConstructorExtraction = $enableConstructorExtraction;
- $this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
- $this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
- $this->magicMethodsFlags = $magicMethodsFlags;
- $this->inflector = $inflector ?? new EnglishInflector();
- $this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
- $this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
- }
- /**
- * {@inheritdoc}
- */
- public function getProperties(string $class, array $context = []): ?array
- {
- try {
- $reflectionClass = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- $reflectionProperties = $reflectionClass->getProperties();
- $properties = [];
- foreach ($reflectionProperties as $reflectionProperty) {
- if ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags) {
- $properties[$reflectionProperty->name] = $reflectionProperty->name;
- }
- }
- foreach ($reflectionClass->getMethods($this->methodReflectionFlags) as $reflectionMethod) {
- if ($reflectionMethod->isStatic()) {
- continue;
- }
- $propertyName = $this->getPropertyName($reflectionMethod->name, $reflectionProperties);
- if (!$propertyName || isset($properties[$propertyName])) {
- continue;
- }
- if ($reflectionClass->hasProperty($lowerCasedPropertyName = lcfirst($propertyName)) || (!$reflectionClass->hasProperty($propertyName) && !preg_match('/^[A-Z]{2,}/', $propertyName))) {
- $propertyName = $lowerCasedPropertyName;
- }
- $properties[$propertyName] = $propertyName;
- }
- return $properties ? array_values($properties) : null;
- }
- /**
- * {@inheritdoc}
- */
- public function getTypes(string $class, string $property, array $context = []): ?array
- {
- if ($fromMutator = $this->extractFromMutator($class, $property)) {
- return $fromMutator;
- }
- if ($fromAccessor = $this->extractFromAccessor($class, $property)) {
- return $fromAccessor;
- }
- if (
- ($context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction) &&
- $fromConstructor = $this->extractFromConstructor($class, $property)
- ) {
- return $fromConstructor;
- }
- if ($fromDefaultValue = $this->extractFromDefaultValue($class, $property)) {
- return $fromDefaultValue;
- }
- if (\PHP_VERSION_ID >= 70400) {
- try {
- $reflectionProperty = new \ReflectionProperty($class, $property);
- $type = $reflectionProperty->getType();
- if (null !== $type) {
- return $this->extractFromReflectionType($type, $reflectionProperty->getDeclaringClass());
- }
- } catch (\ReflectionException $e) {
- // noop
- }
- }
- return null;
- }
- /**
- * {@inheritdoc}
- */
- public function getTypesFromConstructor(string $class, string $property): ?array
- {
- try {
- $reflection = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- if (!$reflectionConstructor = $reflection->getConstructor()) {
- return null;
- }
- if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) {
- return null;
- }
- if (!$reflectionType = $reflectionParameter->getType()) {
- return null;
- }
- if (!$types = $this->extractFromReflectionType($reflectionType, $reflectionConstructor->getDeclaringClass())) {
- return null;
- }
- return $types;
- }
- private function getReflectionParameterFromConstructor(string $property, \ReflectionMethod $reflectionConstructor): ?\ReflectionParameter
- {
- $reflectionParameter = null;
- foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
- if ($reflectionParameter->getName() === $property) {
- return $reflectionParameter;
- }
- }
- return null;
- }
- /**
- * {@inheritdoc}
- */
- public function isReadable(string $class, string $property, array $context = []): ?bool
- {
- if ($this->isAllowedProperty($class, $property)) {
- return true;
- }
- return null !== $this->getReadInfo($class, $property, $context);
- }
- /**
- * {@inheritdoc}
- */
- public function isWritable(string $class, string $property, array $context = []): ?bool
- {
- if ($this->isAllowedProperty($class, $property)) {
- return true;
- }
- [$reflectionMethod] = $this->getMutatorMethod($class, $property);
- return null !== $reflectionMethod;
- }
- /**
- * {@inheritdoc}
- */
- public function isInitializable(string $class, string $property, array $context = []): ?bool
- {
- try {
- $reflectionClass = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- if (!$reflectionClass->isInstantiable()) {
- return false;
- }
- if ($constructor = $reflectionClass->getConstructor()) {
- foreach ($constructor->getParameters() as $parameter) {
- if ($property === $parameter->name) {
- return true;
- }
- }
- } elseif ($parentClass = $reflectionClass->getParentClass()) {
- return $this->isInitializable($parentClass->getName(), $property);
- }
- return false;
- }
- /**
- * {@inheritdoc}
- */
- public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo
- {
- try {
- $reflClass = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
- $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
- $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
- $allowMagicGet = (bool) ($magicMethods & self::ALLOW_MAGIC_GET);
- if (isset($context['enable_magic_call_extraction'])) {
- trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__);
- $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
- }
- $hasProperty = $reflClass->hasProperty($property);
- $camelProp = $this->camelize($property);
- $getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
- foreach ($this->accessorPrefixes as $prefix) {
- $methodName = $prefix.$camelProp;
- if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) {
- $method = $reflClass->getMethod($methodName);
- return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
- }
- }
- if ($allowGetterSetter && $reflClass->hasMethod($getsetter) && ($reflClass->getMethod($getsetter)->getModifiers() & $this->methodReflectionFlags)) {
- $method = $reflClass->getMethod($getsetter);
- return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $getsetter, $this->getReadVisiblityForMethod($method), $method->isStatic(), false);
- }
- if ($hasProperty && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
- $reflProperty = $reflClass->getProperty($property);
- return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, $this->getReadVisiblityForProperty($reflProperty), $reflProperty->isStatic(), true);
- }
- if ($allowMagicGet && $reflClass->hasMethod('__get') && ($reflClass->getMethod('__get')->getModifiers() & $this->methodReflectionFlags)) {
- return new PropertyReadInfo(PropertyReadInfo::TYPE_PROPERTY, $property, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
- }
- if ($allowMagicCall && $reflClass->hasMethod('__call') && ($reflClass->getMethod('__call')->getModifiers() & $this->methodReflectionFlags)) {
- return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, 'get'.$camelProp, PropertyReadInfo::VISIBILITY_PUBLIC, false, false);
- }
- return null;
- }
- /**
- * {@inheritdoc}
- */
- public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo
- {
- try {
- $reflClass = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- $allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
- $magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
- $allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
- $allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET);
- if (isset($context['enable_magic_call_extraction'])) {
- trigger_deprecation('symfony/property-info', '5.2', 'Using the "enable_magic_call_extraction" context option in "%s()" is deprecated. Use "enable_magic_methods_extraction" instead.', __METHOD__);
- $allowMagicCall = $context['enable_magic_call_extraction'] ?? false;
- }
- $allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
- $allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true;
- $camelized = $this->camelize($property);
- $constructor = $reflClass->getConstructor();
- $singulars = $this->inflector->singularize($camelized);
- $errors = [];
- if (null !== $constructor && $allowConstruct) {
- foreach ($constructor->getParameters() as $parameter) {
- if ($parameter->getName() === $property) {
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_CONSTRUCTOR, $property);
- }
- }
- }
- [$adderAccessName, $removerAccessName, $adderAndRemoverErrors] = $this->findAdderAndRemover($reflClass, $singulars);
- if ($allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
- $adderMethod = $reflClass->getMethod($adderAccessName);
- $removerMethod = $reflClass->getMethod($removerAccessName);
- $mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
- $mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisiblityForMethod($adderMethod), $adderMethod->isStatic()));
- $mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisiblityForMethod($removerMethod), $removerMethod->isStatic()));
- return $mutator;
- }
- $errors = array_merge($errors, $adderAndRemoverErrors);
- foreach ($this->mutatorPrefixes as $mutatorPrefix) {
- $methodName = $mutatorPrefix.$camelized;
- [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $methodName, 1);
- if (!$accessible) {
- $errors = array_merge($errors, $methodAccessibleErrors);
- continue;
- }
- $method = $reflClass->getMethod($methodName);
- if (!\in_array($mutatorPrefix, $this->arrayMutatorPrefixes, true)) {
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $methodName, $this->getWriteVisiblityForMethod($method), $method->isStatic());
- }
- }
- $getsetter = lcfirst($camelized);
- if ($allowGetterSetter) {
- [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, $getsetter, 1);
- if ($accessible) {
- $method = $reflClass->getMethod($getsetter);
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $getsetter, $this->getWriteVisiblityForMethod($method), $method->isStatic());
- }
- $errors = array_merge($errors, $methodAccessibleErrors);
- }
- if ($reflClass->hasProperty($property) && ($reflClass->getProperty($property)->getModifiers() & $this->propertyReflectionFlags)) {
- $reflProperty = $reflClass->getProperty($property);
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, $this->getWriteVisiblityForProperty($reflProperty), $reflProperty->isStatic());
- }
- if ($allowMagicSet) {
- [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__set', 2);
- if ($accessible) {
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_PROPERTY, $property, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
- }
- $errors = array_merge($errors, $methodAccessibleErrors);
- }
- if ($allowMagicCall) {
- [$accessible, $methodAccessibleErrors] = $this->isMethodAccessible($reflClass, '__call', 2);
- if ($accessible) {
- return new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, 'set'.$camelized, PropertyWriteInfo::VISIBILITY_PUBLIC, false);
- }
- $errors = array_merge($errors, $methodAccessibleErrors);
- }
- if (!$allowAdderRemover && null !== $adderAccessName && null !== $removerAccessName) {
- $errors[] = sprintf(
- 'The property "%s" in class "%s" can be defined with the methods "%s()" but '.
- 'the new value must be an array or an instance of \Traversable',
- $property,
- $reflClass->getName(),
- implode('()", "', [$adderAccessName, $removerAccessName])
- );
- }
- $noneProperty = new PropertyWriteInfo();
- $noneProperty->setErrors($errors);
- return $noneProperty;
- }
- /**
- * @return Type[]|null
- */
- private function extractFromMutator(string $class, string $property): ?array
- {
- [$reflectionMethod, $prefix] = $this->getMutatorMethod($class, $property);
- if (null === $reflectionMethod) {
- return null;
- }
- $reflectionParameters = $reflectionMethod->getParameters();
- $reflectionParameter = $reflectionParameters[0];
- if (!$reflectionType = $reflectionParameter->getType()) {
- return null;
- }
- $type = $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
- if (1 === \count($type) && \in_array($prefix, $this->arrayMutatorPrefixes)) {
- $type = [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type[0])];
- }
- return $type;
- }
- /**
- * Tries to extract type information from accessors.
- *
- * @return Type[]|null
- */
- private function extractFromAccessor(string $class, string $property): ?array
- {
- [$reflectionMethod, $prefix] = $this->getAccessorMethod($class, $property);
- if (null === $reflectionMethod) {
- return null;
- }
- if ($reflectionType = $reflectionMethod->getReturnType()) {
- return $this->extractFromReflectionType($reflectionType, $reflectionMethod->getDeclaringClass());
- }
- if (\in_array($prefix, ['is', 'can', 'has'])) {
- return [new Type(Type::BUILTIN_TYPE_BOOL)];
- }
- return null;
- }
- /**
- * Tries to extract type information from constructor.
- *
- * @return Type[]|null
- */
- private function extractFromConstructor(string $class, string $property): ?array
- {
- try {
- $reflectionClass = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- $constructor = $reflectionClass->getConstructor();
- if (!$constructor) {
- return null;
- }
- foreach ($constructor->getParameters() as $parameter) {
- if ($property !== $parameter->name) {
- continue;
- }
- $reflectionType = $parameter->getType();
- return $reflectionType ? $this->extractFromReflectionType($reflectionType, $constructor->getDeclaringClass()) : null;
- }
- if ($parentClass = $reflectionClass->getParentClass()) {
- return $this->extractFromConstructor($parentClass->getName(), $property);
- }
- return null;
- }
- private function extractFromDefaultValue(string $class, string $property): ?array
- {
- try {
- $reflectionClass = new \ReflectionClass($class);
- } catch (\ReflectionException $e) {
- return null;
- }
- $defaultValue = $reflectionClass->getDefaultProperties()[$property] ?? null;
- if (null === $defaultValue) {
- return null;
- }
- $type = \gettype($defaultValue);
- $type = static::MAP_TYPES[$type] ?? $type;
- return [new Type($type, false, null, Type::BUILTIN_TYPE_ARRAY === $type)];
- }
- private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
- {
- $types = [];
- $nullable = $reflectionType->allowsNull();
- foreach ($reflectionType instanceof \ReflectionUnionType ? $reflectionType->getTypes() : [$reflectionType] as $type) {
- $phpTypeOrClass = $reflectionType instanceof \ReflectionNamedType ? $reflectionType->getName() : (string) $type;
- if ('null' === $phpTypeOrClass || 'mixed' === $phpTypeOrClass) {
- continue;
- }
- if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
- $types[] = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
- } elseif ('void' === $phpTypeOrClass) {
- $types[] = new Type(Type::BUILTIN_TYPE_NULL, $nullable);
- } elseif ($type->isBuiltin()) {
- $types[] = new Type($phpTypeOrClass, $nullable);
- } else {
- $types[] = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $this->resolveTypeName($phpTypeOrClass, $declaringClass));
- }
- }
- return $types;
- }
- private function resolveTypeName(string $name, \ReflectionClass $declaringClass): string
- {
- if ('self' === $lcName = strtolower($name)) {
- return $declaringClass->name;
- }
- if ('parent' === $lcName && $parent = $declaringClass->getParentClass()) {
- return $parent->name;
- }
- return $name;
- }
- private function isAllowedProperty(string $class, string $property): bool
- {
- try {
- $reflectionProperty = new \ReflectionProperty($class, $property);
- return $reflectionProperty->getModifiers() & $this->propertyReflectionFlags;
- } catch (\ReflectionException $e) {
- // Return false if the property doesn't exist
- }
- return false;
- }
- /**
- * Gets the accessor method.
- *
- * Returns an array with a the instance of \ReflectionMethod as first key
- * and the prefix of the method as second or null if not found.
- */
- private function getAccessorMethod(string $class, string $property): ?array
- {
- $ucProperty = ucfirst($property);
- foreach ($this->accessorPrefixes as $prefix) {
- try {
- $reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty);
- if ($reflectionMethod->isStatic()) {
- continue;
- }
- if (0 === $reflectionMethod->getNumberOfRequiredParameters()) {
- return [$reflectionMethod, $prefix];
- }
- } catch (\ReflectionException $e) {
- // Return null if the property doesn't exist
- }
- }
- return null;
- }
- /**
- * Returns an array with a the instance of \ReflectionMethod as first key
- * and the prefix of the method as second or null if not found.
- */
- private function getMutatorMethod(string $class, string $property): ?array
- {
- $ucProperty = ucfirst($property);
- $ucSingulars = $this->inflector->singularize($ucProperty);
- $mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
- foreach ($mutatorPrefixes as $prefix) {
- $names = [$ucProperty];
- if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
- $names = array_merge($names, $ucSingulars);
- }
- foreach ($names as $name) {
- try {
- $reflectionMethod = new \ReflectionMethod($class, $prefix.$name);
- if ($reflectionMethod->isStatic()) {
- continue;
- }
- // Parameter can be optional to allow things like: method(array $foo = null)
- if ($reflectionMethod->getNumberOfParameters() >= 1) {
- return [$reflectionMethod, $prefix];
- }
- } catch (\ReflectionException $e) {
- // Try the next prefix if the method doesn't exist
- }
- }
- }
- return null;
- }
- private function getPropertyName(string $methodName, array $reflectionProperties): ?string
- {
- $pattern = implode('|', array_merge($this->accessorPrefixes, $this->mutatorPrefixes));
- if ('' !== $pattern && preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) {
- if (!\in_array($matches[1], $this->arrayMutatorPrefixes)) {
- return $matches[2];
- }
- foreach ($reflectionProperties as $reflectionProperty) {
- foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
- if (strtolower($name) === strtolower($matches[2])) {
- return $reflectionProperty->name;
- }
- }
- }
- return $matches[2];
- }
- return null;
- }
- /**
- * Searches for add and remove methods.
- *
- * @param \ReflectionClass $reflClass The reflection class for the given object
- * @param array $singulars The singular form of the property name or null
- *
- * @return array An array containing the adder and remover when found and errors
- */
- private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars): array
- {
- if (!\is_array($this->arrayMutatorPrefixes) && 2 !== \count($this->arrayMutatorPrefixes)) {
- return [null, null, []];
- }
- [$addPrefix, $removePrefix] = $this->arrayMutatorPrefixes;
- $errors = [];
- foreach ($singulars as $singular) {
- $addMethod = $addPrefix.$singular;
- $removeMethod = $removePrefix.$singular;
- [$addMethodFound, $addMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $addMethod, 1);
- [$removeMethodFound, $removeMethodAccessibleErrors] = $this->isMethodAccessible($reflClass, $removeMethod, 1);
- $errors = array_merge($errors, $addMethodAccessibleErrors, $removeMethodAccessibleErrors);
- if ($addMethodFound && $removeMethodFound) {
- return [$addMethod, $removeMethod, []];
- }
- if ($addMethodFound && !$removeMethodFound) {
- $errors[] = sprintf('The add method "%s" in class "%s" was found, but the corresponding remove method "%s" was not found', $addMethod, $reflClass->getName(), $removeMethod);
- } elseif (!$addMethodFound && $removeMethodFound) {
- $errors[] = sprintf('The remove method "%s" in class "%s" was found, but the corresponding add method "%s" was not found', $removeMethod, $reflClass->getName(), $addMethod);
- }
- }
- return [null, null, $errors];
- }
- /**
- * Returns whether a method is public and has the number of required parameters and errors.
- */
- private function isMethodAccessible(\ReflectionClass $class, string $methodName, int $parameters): array
- {
- $errors = [];
- if ($class->hasMethod($methodName)) {
- $method = $class->getMethod($methodName);
- if (\ReflectionMethod::IS_PUBLIC === $this->methodReflectionFlags && !$method->isPublic()) {
- $errors[] = sprintf('The method "%s" in class "%s" was found but does not have public access.', $methodName, $class->getName());
- } elseif ($method->getNumberOfRequiredParameters() > $parameters || $method->getNumberOfParameters() < $parameters) {
- $errors[] = sprintf('The method "%s" in class "%s" requires %d arguments, but should accept only %d.', $methodName, $class->getName(), $method->getNumberOfRequiredParameters(), $parameters);
- } else {
- return [true, $errors];
- }
- }
- return [false, $errors];
- }
- /**
- * Camelizes a given string.
- */
- private function camelize(string $string): string
- {
- return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
- }
- /**
- * Return allowed reflection method flags.
- */
- private function getMethodsFlags(int $accessFlags): int
- {
- $methodFlags = 0;
- if ($accessFlags & self::ALLOW_PUBLIC) {
- $methodFlags |= \ReflectionMethod::IS_PUBLIC;
- }
- if ($accessFlags & self::ALLOW_PRIVATE) {
- $methodFlags |= \ReflectionMethod::IS_PRIVATE;
- }
- if ($accessFlags & self::ALLOW_PROTECTED) {
- $methodFlags |= \ReflectionMethod::IS_PROTECTED;
- }
- return $methodFlags;
- }
- /**
- * Return allowed reflection property flags.
- */
- private function getPropertyFlags(int $accessFlags): int
- {
- $propertyFlags = 0;
- if ($accessFlags & self::ALLOW_PUBLIC) {
- $propertyFlags |= \ReflectionProperty::IS_PUBLIC;
- }
- if ($accessFlags & self::ALLOW_PRIVATE) {
- $propertyFlags |= \ReflectionProperty::IS_PRIVATE;
- }
- if ($accessFlags & self::ALLOW_PROTECTED) {
- $propertyFlags |= \ReflectionProperty::IS_PROTECTED;
- }
- return $propertyFlags;
- }
- private function getReadVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
- {
- if ($reflectionProperty->isPrivate()) {
- return PropertyReadInfo::VISIBILITY_PRIVATE;
- }
- if ($reflectionProperty->isProtected()) {
- return PropertyReadInfo::VISIBILITY_PROTECTED;
- }
- return PropertyReadInfo::VISIBILITY_PUBLIC;
- }
- private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
- {
- if ($reflectionMethod->isPrivate()) {
- return PropertyReadInfo::VISIBILITY_PRIVATE;
- }
- if ($reflectionMethod->isProtected()) {
- return PropertyReadInfo::VISIBILITY_PROTECTED;
- }
- return PropertyReadInfo::VISIBILITY_PUBLIC;
- }
- private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
- {
- if ($reflectionProperty->isPrivate()) {
- return PropertyWriteInfo::VISIBILITY_PRIVATE;
- }
- if ($reflectionProperty->isProtected()) {
- return PropertyWriteInfo::VISIBILITY_PROTECTED;
- }
- return PropertyWriteInfo::VISIBILITY_PUBLIC;
- }
- private function getWriteVisiblityForMethod(\ReflectionMethod $reflectionMethod): string
- {
- if ($reflectionMethod->isPrivate()) {
- return PropertyWriteInfo::VISIBILITY_PRIVATE;
- }
- if ($reflectionMethod->isProtected()) {
- return PropertyWriteInfo::VISIBILITY_PROTECTED;
- }
- return PropertyWriteInfo::VISIBILITY_PUBLIC;
- }
- }
|