Skip to content

Commit 04aafa6

Browse files
Improve QueryResultDynamicReturnTypeExtension
1 parent f769796 commit 04aafa6

File tree

2 files changed

+423
-17
lines changed

2 files changed

+423
-17
lines changed

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

+95-7
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
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;
2225

2326
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -109,12 +112,32 @@ private function getMethodReturnTypeForHydrationMode(
109112
return $this->originalReturnType($methodReflection);
110113
}
111114

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.
115+
if (!$hydrationMode instanceof ConstantIntegerType) {
115116
return $this->originalReturnType($methodReflection);
116117
}
117118

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

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

142-
return $type->getValue() === AbstractQuery::HYDRATE_OBJECT;
230+
return $queryResultType->getFirstIterableValueType();
143231
}
144232

145233
private function originalReturnType(MethodReflection $methodReflection): Type

0 commit comments

Comments
 (0)