From 633cccd2623e227e64589a9f78e54ac88347d197 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 18 Feb 2023 15:05:59 +0100 Subject: [PATCH] Do not use `instanceof *Type` --- ...tityRepositoryClassReflectionExtension.php | 30 ++++----- src/Rules/Doctrine/ORM/EntityColumnRule.php | 6 +- .../Doctrine/ORM/RepositoryMethodCallRule.php | 67 +++++++------------ src/Type/Doctrine/ArgumentsProcessor.php | 8 +-- ...etRepositoryDynamicReturnTypeExtension.php | 36 +++++----- .../QueryResultDynamicReturnTypeExtension.php | 41 ++---------- ...QueryBuilderDynamicReturnTypeExtension.php | 7 +- .../OtherMethodQueryBuilderParser.php | 16 +++-- 8 files changed, 74 insertions(+), 137 deletions(-) diff --git a/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php b/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php index 8367ed9b..799349da 100644 --- a/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php +++ b/src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php @@ -12,7 +12,6 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\NullType; use PHPStan\Type\TypeCombinator; -use PHPStan\Type\TypeWithClassName; use function lcfirst; use function str_replace; use function strlen; @@ -54,10 +53,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): $repositoryAncesor = $classReflection->getAncestorWithClassName(ObjectRepository::class); if ($repositoryAncesor === null) { - $repositoryAncesor = $classReflection->getAncestorWithClassName(ObjectRepository::class); - if ($repositoryAncesor === null) { - return false; - } + return false; } $templateTypeMap = $repositoryAncesor->getActiveTemplateTypeMap(); @@ -66,22 +62,22 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): return false; } - if (!$entityClassType instanceof TypeWithClassName) { - return false; - } + $entityClassNames = $entityClassType->getObjectClassNames(); + $fieldName = $this->classify($methodFieldName); - $classReflection = $entityClassType->getClassReflection(); - if ($classReflection === null) { - return false; - } + /** @var class-string $entityClassName */ + foreach ($entityClassNames as $entityClassName) { + $classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassName); + if ($classMetadata === null) { + continue; + } - $fieldName = $this->classify($methodFieldName); - $classMetadata = $this->objectMetadataResolver->getClassMetadata($classReflection->getName()); - if ($classMetadata === null) { - return false; + if ($classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName)) { + return true; + } } - return $classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName); + return false; } private function classify(string $word): string diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index 3f37525b..9c12bec1 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -160,15 +160,13 @@ public function processNode(Node $node, Scope $scope): array return []; } - $transformArrays = static function (Type $type, callable $traverse): Type { + $propertyTransformedType = TypeTraverser::map($propertyType, static function (Type $type, callable $traverse): Type { if ($type instanceof ArrayType) { return new ArrayType(new MixedType(), new MixedType()); } return $traverse($type); - }; - - $propertyTransformedType = TypeTraverser::map($propertyType, $transformArrays); + }); if (!$propertyTransformedType->isSuperTypeOf($writableToPropertyType)->yes()) { $errors[] = sprintf( diff --git a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php index 64726306..08d12e38 100644 --- a/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php +++ b/src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php @@ -6,11 +6,7 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; -use PHPStan\Type\Constant\ConstantArrayType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Doctrine\ObjectMetadataResolver; -use PHPStan\Type\GenericTypeVariableResolver; -use PHPStan\Type\TypeWithClassName; use PHPStan\Type\VerbosityLevel; use function count; use function in_array; @@ -41,28 +37,12 @@ public function processNode(Node $node, Scope $scope): array return []; } $argType = $scope->getType($node->getArgs()[0]->value); - if (!$argType instanceof ConstantArrayType) { - return []; - } - if (count($argType->getKeyTypes()) === 0) { - return []; - } $calledOnType = $scope->getType($node->var); - if (!$calledOnType instanceof TypeWithClassName) { - return []; - } - $entityClassType = GenericTypeVariableResolver::getType($calledOnType, ObjectRepository::class, 'TEntityClass'); - if ($entityClassType === null) { - $entityClassType = GenericTypeVariableResolver::getType($calledOnType, ObjectRepository::class, 'TEntityClass'); - if ($entityClassType === null) { - return []; - } - } - if (!$entityClassType instanceof TypeWithClassName) { - return []; - } - $entityClassReflection = $entityClassType->getClassReflection(); - if ($entityClassReflection === null) { + $entityClassType = $calledOnType->getTemplateType(ObjectRepository::class, 'TEntityClass'); + + /** @var list $entityClassNames */ + $entityClassNames = $entityClassType->getObjectClassNames(); + if (count($entityClassNames) !== 1) { return []; } @@ -80,32 +60,31 @@ public function processNode(Node $node, Scope $scope): array return []; } - $classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassReflection->getName()); + $classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassNames[0]); if ($classMetadata === null) { return []; } $messages = []; - foreach ($argType->getKeyTypes() as $keyType) { - if (!$keyType instanceof ConstantStringType) { - continue; - } + foreach ($argType->getConstantArrays() as $constantArray) { + foreach ($constantArray->getKeyTypes() as $keyType) { + foreach ($keyType->getConstantStrings() as $fieldName) { + if ( + $classMetadata->hasField($fieldName->getValue()) + || $classMetadata->hasAssociation($fieldName->getValue()) + ) { + continue; + } - $fieldName = $keyType->getValue(); - if ( - $classMetadata->hasField($fieldName) - || $classMetadata->hasAssociation($fieldName) - ) { - continue; + $messages[] = sprintf( + 'Call to method %s::%s() - entity %s does not have a field named $%s.', + $calledOnType->describe(VerbosityLevel::typeOnly()), + $methodName, + $entityClassNames[0], + $fieldName->getValue() + ); + } } - - $messages[] = sprintf( - 'Call to method %s::%s() - entity %s does not have a field named $%s.', - $calledOnType->describe(VerbosityLevel::typeOnly()), - $methodName, - $entityClassReflection->getDisplayName(), - $fieldName - ); } return $messages; diff --git a/src/Type/Doctrine/ArgumentsProcessor.php b/src/Type/Doctrine/ArgumentsProcessor.php index f9913bf9..80e52901 100644 --- a/src/Type/Doctrine/ArgumentsProcessor.php +++ b/src/Type/Doctrine/ArgumentsProcessor.php @@ -5,9 +5,9 @@ use PhpParser\Node\Arg; use PHPStan\Analyser\Scope; use PHPStan\Rules\Doctrine\ORM\DynamicQueryBuilderArgumentException; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\Doctrine\QueryBuilder\Expr\ExprType; +use function count; use function strpos; /** @api */ @@ -35,10 +35,10 @@ public function processArgs( $args[] = $value->getExprObject(); continue; } - if ($value instanceof ConstantArrayType) { + if (count($value->getConstantArrays()) === 1) { $array = []; - foreach ($value->getKeyTypes() as $i => $keyType) { - $valueType = $value->getValueTypes()[$i]; + foreach ($value->getConstantArrays()[0]->getKeyTypes() as $i => $keyType) { + $valueType = $value->getConstantArrays()[0]->getValueTypes()[$i]; if (!$valueType instanceof ConstantScalarType) { throw new DynamicQueryBuilderArgumentException(); } diff --git a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php index 1ccd3f8d..bb3dfd85 100644 --- a/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php @@ -8,20 +8,19 @@ use Doctrine\ODM\MongoDB\Repository\DocumentRepository; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\MappingException; +use Doctrine\Persistence\ObjectRepository; use PhpParser\Node\Arg; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericClassStringType; +use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; use function count; class GetRepositoryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension @@ -91,25 +90,21 @@ public function getTypeFromMethodCall( ); } $argType = $scope->getType($methodCall->getArgs()[0]->value); - if ($argType instanceof ConstantStringType) { - $objectName = $argType->getValue(); - $classType = new ObjectType($objectName); - } elseif ($argType instanceof GenericClassStringType) { - $classType = $argType->getGenericType(); - if (!$classType instanceof TypeWithClassName) { - return new GenericObjectType( - $defaultRepositoryClass, - [$classType] - ); - } - - $objectName = $classType->getClassName(); - } else { + if (!$argType->isClassStringType()->yes()) { return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass); } + $classType = $argType->getClassStringObjectType(); + $objectNames = $classType->getObjectClassNames(); + if (count($objectNames) !== 1) { + return new GenericObjectType( + $defaultRepositoryClass, + [$classType] + ); + } + try { - $repositoryClass = $this->getRepositoryClass($objectName, $defaultRepositoryClass); + $repositoryClass = $this->getRepositoryClass($objectNames[0], $defaultRepositoryClass); } catch (\Doctrine\Persistence\Mapping\MappingException | MappingException | AnnotationException $e) { return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass); } @@ -129,10 +124,11 @@ private function getDefaultReturnType(Scope $scope, array $args, MethodReflectio $args, $methodReflection->getVariants() )->getReturnType(); - if ($defaultType instanceof GenericObjectType && count($defaultType->getTypes()) > 0) { + $entity = $defaultType->getTemplateType(ObjectRepository::class, 'TEntityClass'); + if (!$entity instanceof ErrorType) { return new GenericObjectType( $defaultRepositoryClass, - [$defaultType->getTypes()[0]] + [$entity] ); } diff --git a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php index 1bd41a55..20e23831 100644 --- a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php @@ -12,14 +12,11 @@ use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\GenericTypeVariableResolver; use PHPStan\Type\IntegerType; use PHPStan\Type\IterableType; -use PHPStan\Type\MixedType; use PHPStan\Type\NullType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use PHPStan\Type\TypeWithClassName; use PHPStan\Type\VoidType; final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension @@ -71,45 +68,15 @@ public function getTypeFromMethodCall( } $queryType = $scope->getType($methodCall->var); - $queryResultType = $this->getQueryResultType($queryType); - $queryKeyType = $this->getQueryKeyType($queryType); return $this->getMethodReturnTypeForHydrationMode( $methodReflection, $hydrationMode, - $queryKeyType, - $queryResultType + $queryType->getTemplateType(AbstractQuery::class, 'TKey'), + $queryType->getTemplateType(AbstractQuery::class, 'TResult') ); } - private function getQueryResultType(Type $queryType): Type - { - if (!$queryType instanceof TypeWithClassName) { - return new MixedType(); - } - - $resultType = GenericTypeVariableResolver::getType($queryType, AbstractQuery::class, 'TResult'); - if ($resultType === null) { - return new MixedType(); - } - - return $resultType; - } - - private function getQueryKeyType(Type $queryType): Type - { - if (!$queryType instanceof TypeWithClassName) { - return new MixedType(); - } - - $resultType = GenericTypeVariableResolver::getType($queryType, AbstractQuery::class, 'TKey'); - if ($resultType === null) { - return new MixedType(); - } - - return $resultType; - } - private function getMethodReturnTypeForHydrationMode( MethodReflection $methodReflection, Type $hydrationMode, @@ -144,11 +111,11 @@ private function getMethodReturnTypeForHydrationMode( return TypeCombinator::addNull($queryResultType); case 'toIterable': return new IterableType( - $queryKeyType instanceof NullType ? new IntegerType() : $queryKeyType, + $queryKeyType->isNull()->yes() ? new IntegerType() : $queryKeyType, $queryResultType ); default: - if ($queryKeyType instanceof NullType) { + if ($queryKeyType->isNull()->yes()) { return AccessoryArrayListType::intersectWith(new ArrayType( new IntegerType(), $queryResultType diff --git a/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php index 8f19b08d..a9ea14e5 100644 --- a/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php @@ -10,10 +10,9 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericClassStringType; use PHPStan\Type\Type; -use PHPStan\Type\TypeWithClassName; use function array_unshift; +use function count; class EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -37,8 +36,8 @@ public function getTypeFromMethodCall( $entityNameExpr = new MethodCall($methodCall->var, new Identifier('getEntityName')); $entityNameExprType = $scope->getType($entityNameExpr); - if ($entityNameExprType instanceof GenericClassStringType && $entityNameExprType->getGenericType() instanceof TypeWithClassName) { - $entityNameExpr = new String_($entityNameExprType->getGenericType()->getClassName()); + if ($entityNameExprType->isClassStringType()->yes() && count($entityNameExprType->getClassStringObjectType()->getObjectClassNames()) === 1) { + $entityNameExpr = new String_($entityNameExprType->getClassStringObjectType()->getObjectClassNames()[0]); } if (!isset($methodCall->getArgs()[0])) { diff --git a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php index a9358aad..9c1d76bd 100644 --- a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php +++ b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php @@ -19,7 +19,7 @@ use PHPStan\Parser\Parser; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Generic\TemplateTypeMap; -use PHPStan\Type\TypeWithClassName; +use function count; use function is_array; class OtherMethodQueryBuilderParser @@ -29,7 +29,7 @@ class OtherMethodQueryBuilderParser private $descendIntoOtherMethods; /** @var ReflectionProvider */ - private $broker; + private $reflectionProvider; /** @var Parser */ private $parser; @@ -37,10 +37,10 @@ class OtherMethodQueryBuilderParser /** @var Container */ private $container; - public function __construct(bool $descendIntoOtherMethods, ReflectionProvider $broker, Parser $parser, Container $container) + public function __construct(bool $descendIntoOtherMethods, ReflectionProvider $reflectionProvider, Parser $parser, Container $container) { $this->descendIntoOtherMethods = $descendIntoOtherMethods; - $this->broker = $broker; + $this->reflectionProvider = $reflectionProvider; $this->parser = $parser; $this->container = $container; } @@ -66,15 +66,17 @@ private function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodCall $m return []; } - if (!$methodCalledOnType instanceof TypeWithClassName) { + $methodCalledOnTypeClassNames = $methodCalledOnType->getObjectClassNames(); + + if (count($methodCalledOnTypeClassNames) !== 1) { return []; } - if (!$this->broker->hasClass($methodCalledOnType->getClassName())) { + if (!$this->reflectionProvider->hasClass($methodCalledOnTypeClassNames[0])) { return []; } - $classReflection = $this->broker->getClass($methodCalledOnType->getClassName()); + $classReflection = $this->reflectionProvider->getClass($methodCalledOnTypeClassNames[0]); $methodName = $methodCall->name->toString(); if (!$classReflection->hasNativeMethod($methodName)) { return [];