Skip to content

Commit 45b6fe9

Browse files
committed
EntityColumnRule, EntityRelationRule - read property type from ClassPropertyNode
1 parent 6f700f8 commit 45b6fe9

File tree

4 files changed

+53
-38
lines changed

4 files changed

+53
-38
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
],
88
"require": {
99
"php": "^7.2 || ^8.0",
10-
"phpstan/phpstan": "^1.8.6"
10+
"phpstan/phpstan": "^1.8.11"
1111
},
1212
"conflict": {
1313
"doctrine/collections": "<1.0",

phpstan-baseline.neon

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ parameters:
55
count: 1
66
path: src/Doctrine/Mapping/ClassMetadataFactory.php
77

8+
-
9+
message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
10+
count: 1
11+
path: src/Rules/Doctrine/ORM/EntityColumnRule.php
12+
13+
-
14+
message: "#^Calling PHPStan\\\\Type\\\\TypehintHelper\\:\\:decideType\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
15+
count: 1
16+
path: src/Rules/Doctrine/ORM/EntityColumnRule.php
17+
18+
-
19+
message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
20+
count: 1
21+
path: src/Rules/Doctrine/ORM/EntityRelationRule.php
22+
23+
-
24+
message: "#^Calling PHPStan\\\\Type\\\\TypehintHelper\\:\\:decideType\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
25+
count: 1
26+
path: src/Rules/Doctrine/ORM/EntityRelationRule.php
27+
828
-
929
message: "#^Call to method getProperty\\(\\) on an unknown class PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#"
1030
count: 1

src/Rules/Doctrine/ORM/EntityColumnRule.php

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\ClassPropertyNode;
8-
use PHPStan\Reflection\MissingPropertyFromReflectionException;
98
use PHPStan\Reflection\ReflectionProvider;
109
use PHPStan\Rules\Rule;
1110
use PHPStan\Type\ArrayType;
@@ -16,8 +15,10 @@
1615
use PHPStan\Type\MixedType;
1716
use PHPStan\Type\NeverType;
1817
use PHPStan\Type\ObjectType;
18+
use PHPStan\Type\ParserNodeTypeToPHPStanType;
1919
use PHPStan\Type\Type;
2020
use PHPStan\Type\TypeCombinator;
21+
use PHPStan\Type\TypehintHelper;
2122
use PHPStan\Type\TypeTraverser;
2223
use PHPStan\Type\VerbosityLevel;
2324
use Throwable;
@@ -88,12 +89,6 @@ public function processNode(Node $node, Scope $scope): array
8889
}
8990

9091
$propertyName = $node->getName();
91-
try {
92-
$property = $class->getNativeProperty($propertyName);
93-
} catch (MissingPropertyFromReflectionException $e) {
94-
return [];
95-
}
96-
9792
if (!isset($metadata->fieldMappings[$propertyName])) {
9893
return [];
9994
}
@@ -157,8 +152,11 @@ public function processNode(Node $node, Scope $scope): array
157152
$writableToDatabaseType = TypeCombinator::addNull($writableToDatabaseType);
158153
}
159154

160-
$propertyWritableType = $property->getWritableType();
161-
if (get_class($propertyWritableType) === MixedType::class || $propertyWritableType instanceof ErrorType || $propertyWritableType instanceof NeverType) {
155+
$phpDocType = $node->getPhpDocType();
156+
$nativeType = $node->getNativeType() !== null ? ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()) : new MixedType();
157+
$propertyType = TypehintHelper::decideType($nativeType, $phpDocType);
158+
159+
if (get_class($propertyType) === MixedType::class || $propertyType instanceof ErrorType || $propertyType instanceof NeverType) {
162160
return [];
163161
}
164162

@@ -170,32 +168,31 @@ public function processNode(Node $node, Scope $scope): array
170168
return $traverse($type);
171169
};
172170

173-
$propertyWritableType = TypeTraverser::map($propertyWritableType, $transformArrays);
171+
$propertyTransformedType = TypeTraverser::map($propertyType, $transformArrays);
174172

175-
if (!$propertyWritableType->isSuperTypeOf($writableToPropertyType)->yes()) {
173+
if (!$propertyTransformedType->isSuperTypeOf($writableToPropertyType)->yes()) {
176174
$errors[] = sprintf(
177175
'Property %s::$%s type mapping mismatch: database can contain %s but property expects %s.',
178176
$className,
179177
$propertyName,
180-
$writableToPropertyType->describe(VerbosityLevel::getRecommendedLevelByType($propertyWritableType, $writableToPropertyType)),
181-
$property->getWritableType()->describe(VerbosityLevel::getRecommendedLevelByType($propertyWritableType, $writableToPropertyType))
178+
$writableToPropertyType->describe(VerbosityLevel::getRecommendedLevelByType($propertyTransformedType, $writableToPropertyType)),
179+
$propertyType->describe(VerbosityLevel::getRecommendedLevelByType($propertyTransformedType, $writableToPropertyType))
182180
);
183181
}
184-
$propertyReadableType = TypeTraverser::map($property->getReadableType(), $transformArrays);
185182

186183
if (
187184
!$writableToDatabaseType->isSuperTypeOf(
188185
$this->allowNullablePropertyForRequiredField || (in_array($propertyName, $identifiers, true) && !$nullable)
189-
? TypeCombinator::removeNull($propertyReadableType)
190-
: $propertyReadableType
186+
? TypeCombinator::removeNull($propertyTransformedType)
187+
: $propertyTransformedType
191188
)->yes()
192189
) {
193190
$errors[] = sprintf(
194191
'Property %s::$%s type mapping mismatch: property can contain %s but database expects %s.',
195192
$className,
196193
$propertyName,
197-
$propertyReadableType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyReadableType)),
198-
$writableToDatabaseType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyReadableType))
194+
$propertyTransformedType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyTransformedType)),
195+
$writableToDatabaseType->describe(VerbosityLevel::getRecommendedLevelByType($writableToDatabaseType, $propertyTransformedType))
199196
);
200197
}
201198
return $errors;

src/Rules/Doctrine/ORM/EntityRelationRule.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\ClassPropertyNode;
8-
use PHPStan\Reflection\MissingPropertyFromReflectionException;
98
use PHPStan\Rules\Rule;
109
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
1110
use PHPStan\Type\ErrorType;
1211
use PHPStan\Type\IterableType;
1312
use PHPStan\Type\MixedType;
1413
use PHPStan\Type\NeverType;
1514
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\ParserNodeTypeToPHPStanType;
1616
use PHPStan\Type\TypeCombinator;
17+
use PHPStan\Type\TypehintHelper;
1718
use PHPStan\Type\VerbosityLevel;
1819
use Throwable;
1920
use function get_class;
@@ -69,12 +70,6 @@ public function processNode(Node $node, Scope $scope): array
6970
}
7071

7172
$propertyName = $node->getName();
72-
try {
73-
$property = $class->getNativeProperty($propertyName);
74-
} catch (MissingPropertyFromReflectionException $e) {
75-
return [];
76-
}
77-
7873
if (!isset($metadata->associationMappings[$propertyName])) {
7974
return [];
8075
}
@@ -110,46 +105,49 @@ public function processNode(Node $node, Scope $scope): array
110105
);
111106
}
112107

108+
$phpDocType = $node->getPhpDocType();
109+
$nativeType = $node->getNativeType() !== null ? ParserNodeTypeToPHPStanType::resolve($node->getNativeType(), $scope->getClassReflection()) : new MixedType();
110+
$propertyType = TypehintHelper::decideType($nativeType, $phpDocType);
111+
113112
$errors = [];
114113
if ($columnType !== null) {
115-
$propertyWritableType = $property->getWritableType();
116-
if (get_class($propertyWritableType) === MixedType::class || $propertyWritableType instanceof ErrorType || $propertyWritableType instanceof NeverType) {
114+
if (get_class($propertyType) === MixedType::class || $propertyType instanceof ErrorType || $propertyType instanceof NeverType) {
117115
return [];
118116
}
119117

120118
$collectionObjectType = new ObjectType('Doctrine\Common\Collections\Collection');
121-
$propertyWritableTypeToCheckAgainst = $propertyWritableType;
119+
$propertyTypeToCheckAgainst = $propertyType;
122120
if (
123121
$toMany
124-
&& $collectionObjectType->isSuperTypeOf($propertyWritableType)->yes()
125-
&& $propertyWritableType->isIterable()->yes()
122+
&& $collectionObjectType->isSuperTypeOf($propertyType)->yes()
123+
&& $propertyType->isIterable()->yes()
126124
) {
127-
$propertyWritableTypeToCheckAgainst = TypeCombinator::intersect(
125+
$propertyTypeToCheckAgainst = TypeCombinator::intersect(
128126
$collectionObjectType,
129-
new IterableType(new MixedType(true), $propertyWritableType->getIterableValueType())
127+
new IterableType(new MixedType(true), $propertyType->getIterableValueType())
130128
);
131129
}
132-
if (!$propertyWritableTypeToCheckAgainst->isSuperTypeOf($columnType)->yes()) {
130+
if (!$propertyTypeToCheckAgainst->isSuperTypeOf($columnType)->yes()) {
133131
$errors[] = sprintf(
134132
'Property %s::$%s type mapping mismatch: database can contain %s but property expects %s.',
135133
$className,
136134
$propertyName,
137135
$columnType->describe(VerbosityLevel::typeOnly()),
138-
$propertyWritableType->describe(VerbosityLevel::typeOnly())
136+
$propertyType->describe(VerbosityLevel::typeOnly())
139137
);
140138
}
141139
if (
142140
!$columnType->isSuperTypeOf(
143141
$this->allowNullablePropertyForRequiredField
144-
? TypeCombinator::removeNull($property->getReadableType())
145-
: $property->getReadableType()
142+
? TypeCombinator::removeNull($propertyType)
143+
: $propertyType
146144
)->yes()
147145
) {
148146
$errors[] = sprintf(
149147
'Property %s::$%s type mapping mismatch: property can contain %s but database expects %s.',
150148
$className,
151149
$propertyName,
152-
$property->getReadableType()->describe(VerbosityLevel::typeOnly()),
150+
$propertyType->describe(VerbosityLevel::typeOnly()),
153151
$columnType->describe(VerbosityLevel::typeOnly())
154152
);
155153
}

0 commit comments

Comments
 (0)