Skip to content

Commit 633cccd

Browse files
committed
Do not use instanceof *Type
1 parent 05db8be commit 633cccd

8 files changed

+74
-137
lines changed

src/Reflection/Doctrine/EntityRepositoryClassReflectionExtension.php

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use PHPStan\Type\IntegerType;
1313
use PHPStan\Type\NullType;
1414
use PHPStan\Type\TypeCombinator;
15-
use PHPStan\Type\TypeWithClassName;
1615
use function lcfirst;
1716
use function str_replace;
1817
use function strlen;
@@ -54,10 +53,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName):
5453

5554
$repositoryAncesor = $classReflection->getAncestorWithClassName(ObjectRepository::class);
5655
if ($repositoryAncesor === null) {
57-
$repositoryAncesor = $classReflection->getAncestorWithClassName(ObjectRepository::class);
58-
if ($repositoryAncesor === null) {
59-
return false;
60-
}
56+
return false;
6157
}
6258

6359
$templateTypeMap = $repositoryAncesor->getActiveTemplateTypeMap();
@@ -66,22 +62,22 @@ public function hasMethod(ClassReflection $classReflection, string $methodName):
6662
return false;
6763
}
6864

69-
if (!$entityClassType instanceof TypeWithClassName) {
70-
return false;
71-
}
65+
$entityClassNames = $entityClassType->getObjectClassNames();
66+
$fieldName = $this->classify($methodFieldName);
7267

73-
$classReflection = $entityClassType->getClassReflection();
74-
if ($classReflection === null) {
75-
return false;
76-
}
68+
/** @var class-string $entityClassName */
69+
foreach ($entityClassNames as $entityClassName) {
70+
$classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassName);
71+
if ($classMetadata === null) {
72+
continue;
73+
}
7774

78-
$fieldName = $this->classify($methodFieldName);
79-
$classMetadata = $this->objectMetadataResolver->getClassMetadata($classReflection->getName());
80-
if ($classMetadata === null) {
81-
return false;
75+
if ($classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName)) {
76+
return true;
77+
}
8278
}
8379

84-
return $classMetadata->hasField($fieldName) || $classMetadata->hasAssociation($fieldName);
80+
return false;
8581
}
8682

8783
private function classify(string $word): string

src/Rules/Doctrine/ORM/EntityColumnRule.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,13 @@ public function processNode(Node $node, Scope $scope): array
160160
return [];
161161
}
162162

163-
$transformArrays = static function (Type $type, callable $traverse): Type {
163+
$propertyTransformedType = TypeTraverser::map($propertyType, static function (Type $type, callable $traverse): Type {
164164
if ($type instanceof ArrayType) {
165165
return new ArrayType(new MixedType(), new MixedType());
166166
}
167167

168168
return $traverse($type);
169-
};
170-
171-
$propertyTransformedType = TypeTraverser::map($propertyType, $transformArrays);
169+
});
172170

173171
if (!$propertyTransformedType->isSuperTypeOf($writableToPropertyType)->yes()) {
174172
$errors[] = sprintf(

src/Rules/Doctrine/ORM/RepositoryMethodCallRule.php

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@
66
use PhpParser\Node;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Rules\Rule;
9-
use PHPStan\Type\Constant\ConstantArrayType;
10-
use PHPStan\Type\Constant\ConstantStringType;
119
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
12-
use PHPStan\Type\GenericTypeVariableResolver;
13-
use PHPStan\Type\TypeWithClassName;
1410
use PHPStan\Type\VerbosityLevel;
1511
use function count;
1612
use function in_array;
@@ -41,28 +37,12 @@ public function processNode(Node $node, Scope $scope): array
4137
return [];
4238
}
4339
$argType = $scope->getType($node->getArgs()[0]->value);
44-
if (!$argType instanceof ConstantArrayType) {
45-
return [];
46-
}
47-
if (count($argType->getKeyTypes()) === 0) {
48-
return [];
49-
}
5040
$calledOnType = $scope->getType($node->var);
51-
if (!$calledOnType instanceof TypeWithClassName) {
52-
return [];
53-
}
54-
$entityClassType = GenericTypeVariableResolver::getType($calledOnType, ObjectRepository::class, 'TEntityClass');
55-
if ($entityClassType === null) {
56-
$entityClassType = GenericTypeVariableResolver::getType($calledOnType, ObjectRepository::class, 'TEntityClass');
57-
if ($entityClassType === null) {
58-
return [];
59-
}
60-
}
61-
if (!$entityClassType instanceof TypeWithClassName) {
62-
return [];
63-
}
64-
$entityClassReflection = $entityClassType->getClassReflection();
65-
if ($entityClassReflection === null) {
41+
$entityClassType = $calledOnType->getTemplateType(ObjectRepository::class, 'TEntityClass');
42+
43+
/** @var list<class-string> $entityClassNames */
44+
$entityClassNames = $entityClassType->getObjectClassNames();
45+
if (count($entityClassNames) !== 1) {
6646
return [];
6747
}
6848

@@ -80,32 +60,31 @@ public function processNode(Node $node, Scope $scope): array
8060
return [];
8161
}
8262

83-
$classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassReflection->getName());
63+
$classMetadata = $this->objectMetadataResolver->getClassMetadata($entityClassNames[0]);
8464
if ($classMetadata === null) {
8565
return [];
8666
}
8767

8868
$messages = [];
89-
foreach ($argType->getKeyTypes() as $keyType) {
90-
if (!$keyType instanceof ConstantStringType) {
91-
continue;
92-
}
69+
foreach ($argType->getConstantArrays() as $constantArray) {
70+
foreach ($constantArray->getKeyTypes() as $keyType) {
71+
foreach ($keyType->getConstantStrings() as $fieldName) {
72+
if (
73+
$classMetadata->hasField($fieldName->getValue())
74+
|| $classMetadata->hasAssociation($fieldName->getValue())
75+
) {
76+
continue;
77+
}
9378

94-
$fieldName = $keyType->getValue();
95-
if (
96-
$classMetadata->hasField($fieldName)
97-
|| $classMetadata->hasAssociation($fieldName)
98-
) {
99-
continue;
79+
$messages[] = sprintf(
80+
'Call to method %s::%s() - entity %s does not have a field named $%s.',
81+
$calledOnType->describe(VerbosityLevel::typeOnly()),
82+
$methodName,
83+
$entityClassNames[0],
84+
$fieldName->getValue()
85+
);
86+
}
10087
}
101-
102-
$messages[] = sprintf(
103-
'Call to method %s::%s() - entity %s does not have a field named $%s.',
104-
$calledOnType->describe(VerbosityLevel::typeOnly()),
105-
$methodName,
106-
$entityClassReflection->getDisplayName(),
107-
$fieldName
108-
);
10988
}
11089

11190
return $messages;

src/Type/Doctrine/ArgumentsProcessor.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
use PhpParser\Node\Arg;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\Doctrine\ORM\DynamicQueryBuilderArgumentException;
8-
use PHPStan\Type\Constant\ConstantArrayType;
98
use PHPStan\Type\ConstantScalarType;
109
use PHPStan\Type\Doctrine\QueryBuilder\Expr\ExprType;
10+
use function count;
1111
use function strpos;
1212

1313
/** @api */
@@ -35,10 +35,10 @@ public function processArgs(
3535
$args[] = $value->getExprObject();
3636
continue;
3737
}
38-
if ($value instanceof ConstantArrayType) {
38+
if (count($value->getConstantArrays()) === 1) {
3939
$array = [];
40-
foreach ($value->getKeyTypes() as $i => $keyType) {
41-
$valueType = $value->getValueTypes()[$i];
40+
foreach ($value->getConstantArrays()[0]->getKeyTypes() as $i => $keyType) {
41+
$valueType = $value->getConstantArrays()[0]->getValueTypes()[$i];
4242
if (!$valueType instanceof ConstantScalarType) {
4343
throw new DynamicQueryBuilderArgumentException();
4444
}

src/Type/Doctrine/GetRepositoryDynamicReturnTypeExtension.php

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,19 @@
88
use Doctrine\ODM\MongoDB\Repository\DocumentRepository;
99
use Doctrine\ORM\EntityRepository;
1010
use Doctrine\ORM\Mapping\MappingException;
11+
use Doctrine\Persistence\ObjectRepository;
1112
use PhpParser\Node\Arg;
1213
use PhpParser\Node\Expr\MethodCall;
1314
use PHPStan\Analyser\Scope;
1415
use PHPStan\Reflection\MethodReflection;
1516
use PHPStan\Reflection\ParametersAcceptorSelector;
1617
use PHPStan\Reflection\ReflectionProvider;
17-
use PHPStan\Type\Constant\ConstantStringType;
1818
use PHPStan\Type\DynamicMethodReturnTypeExtension;
19-
use PHPStan\Type\Generic\GenericClassStringType;
19+
use PHPStan\Type\ErrorType;
2020
use PHPStan\Type\Generic\GenericObjectType;
2121
use PHPStan\Type\ObjectType;
2222
use PHPStan\Type\ObjectWithoutClassType;
2323
use PHPStan\Type\Type;
24-
use PHPStan\Type\TypeWithClassName;
2524
use function count;
2625

2726
class GetRepositoryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -91,25 +90,21 @@ public function getTypeFromMethodCall(
9190
);
9291
}
9392
$argType = $scope->getType($methodCall->getArgs()[0]->value);
94-
if ($argType instanceof ConstantStringType) {
95-
$objectName = $argType->getValue();
96-
$classType = new ObjectType($objectName);
97-
} elseif ($argType instanceof GenericClassStringType) {
98-
$classType = $argType->getGenericType();
99-
if (!$classType instanceof TypeWithClassName) {
100-
return new GenericObjectType(
101-
$defaultRepositoryClass,
102-
[$classType]
103-
);
104-
}
105-
106-
$objectName = $classType->getClassName();
107-
} else {
93+
if (!$argType->isClassStringType()->yes()) {
10894
return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass);
10995
}
11096

97+
$classType = $argType->getClassStringObjectType();
98+
$objectNames = $classType->getObjectClassNames();
99+
if (count($objectNames) !== 1) {
100+
return new GenericObjectType(
101+
$defaultRepositoryClass,
102+
[$classType]
103+
);
104+
}
105+
111106
try {
112-
$repositoryClass = $this->getRepositoryClass($objectName, $defaultRepositoryClass);
107+
$repositoryClass = $this->getRepositoryClass($objectNames[0], $defaultRepositoryClass);
113108
} catch (\Doctrine\Persistence\Mapping\MappingException | MappingException | AnnotationException $e) {
114109
return $this->getDefaultReturnType($scope, $methodCall->getArgs(), $methodReflection, $defaultRepositoryClass);
115110
}
@@ -129,10 +124,11 @@ private function getDefaultReturnType(Scope $scope, array $args, MethodReflectio
129124
$args,
130125
$methodReflection->getVariants()
131126
)->getReturnType();
132-
if ($defaultType instanceof GenericObjectType && count($defaultType->getTypes()) > 0) {
127+
$entity = $defaultType->getTemplateType(ObjectRepository::class, 'TEntityClass');
128+
if (!$entity instanceof ErrorType) {
133129
return new GenericObjectType(
134130
$defaultRepositoryClass,
135-
[$defaultType->getTypes()[0]]
131+
[$entity]
136132
);
137133
}
138134

src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212
use PHPStan\Type\ArrayType;
1313
use PHPStan\Type\Constant\ConstantIntegerType;
1414
use PHPStan\Type\DynamicMethodReturnTypeExtension;
15-
use PHPStan\Type\GenericTypeVariableResolver;
1615
use PHPStan\Type\IntegerType;
1716
use PHPStan\Type\IterableType;
18-
use PHPStan\Type\MixedType;
1917
use PHPStan\Type\NullType;
2018
use PHPStan\Type\Type;
2119
use PHPStan\Type\TypeCombinator;
22-
use PHPStan\Type\TypeWithClassName;
2320
use PHPStan\Type\VoidType;
2421

2522
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -71,45 +68,15 @@ public function getTypeFromMethodCall(
7168
}
7269

7370
$queryType = $scope->getType($methodCall->var);
74-
$queryResultType = $this->getQueryResultType($queryType);
75-
$queryKeyType = $this->getQueryKeyType($queryType);
7671

7772
return $this->getMethodReturnTypeForHydrationMode(
7873
$methodReflection,
7974
$hydrationMode,
80-
$queryKeyType,
81-
$queryResultType
75+
$queryType->getTemplateType(AbstractQuery::class, 'TKey'),
76+
$queryType->getTemplateType(AbstractQuery::class, 'TResult')
8277
);
8378
}
8479

85-
private function getQueryResultType(Type $queryType): Type
86-
{
87-
if (!$queryType instanceof TypeWithClassName) {
88-
return new MixedType();
89-
}
90-
91-
$resultType = GenericTypeVariableResolver::getType($queryType, AbstractQuery::class, 'TResult');
92-
if ($resultType === null) {
93-
return new MixedType();
94-
}
95-
96-
return $resultType;
97-
}
98-
99-
private function getQueryKeyType(Type $queryType): Type
100-
{
101-
if (!$queryType instanceof TypeWithClassName) {
102-
return new MixedType();
103-
}
104-
105-
$resultType = GenericTypeVariableResolver::getType($queryType, AbstractQuery::class, 'TKey');
106-
if ($resultType === null) {
107-
return new MixedType();
108-
}
109-
110-
return $resultType;
111-
}
112-
11380
private function getMethodReturnTypeForHydrationMode(
11481
MethodReflection $methodReflection,
11582
Type $hydrationMode,
@@ -144,11 +111,11 @@ private function getMethodReturnTypeForHydrationMode(
144111
return TypeCombinator::addNull($queryResultType);
145112
case 'toIterable':
146113
return new IterableType(
147-
$queryKeyType instanceof NullType ? new IntegerType() : $queryKeyType,
114+
$queryKeyType->isNull()->yes() ? new IntegerType() : $queryKeyType,
148115
$queryResultType
149116
);
150117
default:
151-
if ($queryKeyType instanceof NullType) {
118+
if ($queryKeyType->isNull()->yes()) {
152119
return AccessoryArrayListType::intersectWith(new ArrayType(
153120
new IntegerType(),
154121
$queryResultType

src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
use PHPStan\Reflection\MethodReflection;
1111
use PHPStan\Reflection\ParametersAcceptorSelector;
1212
use PHPStan\Type\DynamicMethodReturnTypeExtension;
13-
use PHPStan\Type\Generic\GenericClassStringType;
1413
use PHPStan\Type\Type;
15-
use PHPStan\Type\TypeWithClassName;
1614
use function array_unshift;
15+
use function count;
1716

1817
class EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
1918
{
@@ -37,8 +36,8 @@ public function getTypeFromMethodCall(
3736
$entityNameExpr = new MethodCall($methodCall->var, new Identifier('getEntityName'));
3837

3938
$entityNameExprType = $scope->getType($entityNameExpr);
40-
if ($entityNameExprType instanceof GenericClassStringType && $entityNameExprType->getGenericType() instanceof TypeWithClassName) {
41-
$entityNameExpr = new String_($entityNameExprType->getGenericType()->getClassName());
39+
if ($entityNameExprType->isClassStringType()->yes() && count($entityNameExprType->getClassStringObjectType()->getObjectClassNames()) === 1) {
40+
$entityNameExpr = new String_($entityNameExprType->getClassStringObjectType()->getObjectClassNames()[0]);
4241
}
4342

4443
if (!isset($methodCall->getArgs()[0])) {

0 commit comments

Comments
 (0)