Skip to content

Commit cdc7f30

Browse files
Improve QueryResultDynamicReturnTypeExtension
1 parent f769796 commit cdc7f30

File tree

3 files changed

+425
-18
lines changed

3 files changed

+425
-18
lines changed

Diff for: composer.json

+1-1
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.11"
10+
"phpstan/phpstan": "^1.9.0"
1111
},
1212
"conflict": {
1313
"doctrine/collections": "<1.0",

Diff for: src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php

+96-7
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@
99
use PHPStan\Reflection\ParametersAcceptorSelector;
1010
use PHPStan\ShouldNotHappenException;
1111
use PHPStan\Type\ArrayType;
12+
use PHPStan\Type\Constant\ConstantArrayType;
1213
use PHPStan\Type\Constant\ConstantIntegerType;
1314
use PHPStan\Type\DynamicMethodReturnTypeExtension;
1415
use PHPStan\Type\Generic\GenericObjectType;
1516
use PHPStan\Type\IntegerType;
1617
use PHPStan\Type\IterableType;
1718
use PHPStan\Type\MixedType;
1819
use PHPStan\Type\NullType;
20+
use PHPStan\Type\ObjectWithoutClassType;
1921
use PHPStan\Type\Type;
2022
use PHPStan\Type\TypeCombinator;
23+
use PHPStan\Type\TypeTraverser;
2124
use PHPStan\Type\VoidType;
25+
use function count;
2226

2327
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
2428
{
@@ -109,12 +113,32 @@ private function getMethodReturnTypeForHydrationMode(
109113
return $this->originalReturnType($methodReflection);
110114
}
111115

112-
if (!$this->isObjectHydrationMode($hydrationMode)) {
113-
// We support only HYDRATE_OBJECT. For other hydration modes, we
114-
// return the declared return type of the method.
116+
if (!$hydrationMode instanceof ConstantIntegerType) {
115117
return $this->originalReturnType($methodReflection);
116118
}
117119

120+
switch ($hydrationMode->getValue()) {
121+
case AbstractQuery::HYDRATE_OBJECT:
122+
break;
123+
case AbstractQuery::HYDRATE_ARRAY:
124+
$queryResultType = $this->getArrayHydratedReturnType($queryResultType);
125+
break;
126+
case AbstractQuery::HYDRATE_SCALAR:
127+
$queryResultType = $this->getScalarHydratedReturnType($queryResultType);
128+
break;
129+
case AbstractQuery::HYDRATE_SINGLE_SCALAR:
130+
$queryResultType = $this->getSingleScalarHydratedReturnType($queryResultType);
131+
break;
132+
case AbstractQuery::HYDRATE_SIMPLEOBJECT:
133+
$queryResultType = $this->getSimpleObjectHydratedReturnType($queryResultType);
134+
break;
135+
case AbstractQuery::HYDRATE_SCALAR_COLUMN:
136+
$queryResultType = $this->getScalarColumnHydratedReturnType($queryResultType);
137+
break;
138+
default:
139+
return $this->originalReturnType($methodReflection);
140+
}
141+
118142
switch ($methodReflection->getName()) {
119143
case 'getSingleResult':
120144
return $queryResultType;
@@ -133,13 +157,78 @@ private function getMethodReturnTypeForHydrationMode(
133157
}
134158
}
135159

136-
private function isObjectHydrationMode(Type $type): bool
160+
private function getArrayHydratedReturnType(Type $queryResultType): Type
161+
{
162+
return TypeTraverser::map(
163+
$queryResultType,
164+
static function (Type $type, callable $traverse): Type {
165+
$isObject = (new ObjectWithoutClassType())->isSuperTypeOf($type);
166+
if ($isObject->yes()) {
167+
return new ArrayType(new MixedType(), new MixedType());
168+
}
169+
if ($isObject->maybe()) {
170+
return new MixedType();
171+
}
172+
173+
return $traverse($type);
174+
}
175+
);
176+
}
177+
178+
private function getScalarHydratedReturnType(Type $queryResultType): Type
179+
{
180+
if (!$queryResultType instanceof ArrayType) {
181+
return new ArrayType(new MixedType(), new MixedType());
182+
}
183+
184+
$itemType = $queryResultType->getItemType();
185+
$hasNoObject = (new ObjectWithoutClassType())->isSuperTypeOf($itemType)->no();
186+
$hasNoArray = $itemType->isArray()->no();
187+
188+
if ($hasNoArray && $hasNoObject) {
189+
return $queryResultType;
190+
}
191+
192+
return new ArrayType(new MixedType(), new MixedType());
193+
}
194+
195+
private function getSimpleObjectHydratedReturnType(Type $queryResultType): Type
137196
{
138-
if (!$type instanceof ConstantIntegerType) {
139-
return false;
197+
if ((new ObjectWithoutClassType())->isSuperTypeOf($queryResultType)->yes()) {
198+
return $queryResultType;
199+
}
200+
201+
return new MixedType();
202+
}
203+
204+
private function getSingleScalarHydratedReturnType(Type $queryResultType): Type
205+
{
206+
$queryResultType = $this->getScalarHydratedReturnType($queryResultType);
207+
if (!$queryResultType instanceof ConstantArrayType) {
208+
return new ArrayType(new MixedType(), new MixedType());
209+
}
210+
211+
$values = $queryResultType->getValueTypes();
212+
if (count($values) !== 1) {
213+
return new ArrayType(new MixedType(), new MixedType());
214+
}
215+
216+
return $queryResultType;
217+
}
218+
219+
private function getScalarColumnHydratedReturnType(Type $queryResultType): Type
220+
{
221+
$queryResultType = $this->getScalarHydratedReturnType($queryResultType);
222+
if (!$queryResultType instanceof ConstantArrayType) {
223+
return new MixedType();
224+
}
225+
226+
$values = $queryResultType->getValueTypes();
227+
if (count($values) !== 1) {
228+
return new MixedType();
140229
}
141230

142-
return $type->getValue() === AbstractQuery::HYDRATE_OBJECT;
231+
return $queryResultType->getFirstIterableValueType();
143232
}
144233

145234
private function originalReturnType(MethodReflection $methodReflection): Type

0 commit comments

Comments
 (0)