10
10
use PHPStan \ShouldNotHappenException ;
11
11
use PHPStan \Type \Accessory \AccessoryArrayListType ;
12
12
use PHPStan \Type \ArrayType ;
13
+ use PHPStan \Type \Constant \ConstantArrayType ;
13
14
use PHPStan \Type \Constant \ConstantIntegerType ;
14
15
use PHPStan \Type \DynamicMethodReturnTypeExtension ;
15
16
use PHPStan \Type \GenericTypeVariableResolver ;
16
17
use PHPStan \Type \IntegerType ;
17
18
use PHPStan \Type \IterableType ;
18
19
use PHPStan \Type \MixedType ;
19
20
use PHPStan \Type \NullType ;
21
+ use PHPStan \Type \ObjectWithoutClassType ;
20
22
use PHPStan \Type \Type ;
21
23
use PHPStan \Type \TypeCombinator ;
24
+ use PHPStan \Type \TypeTraverser ;
22
25
use PHPStan \Type \TypeWithClassName ;
23
26
use PHPStan \Type \VoidType ;
27
+ use function count ;
24
28
25
29
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
26
30
{
@@ -35,14 +39,22 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
35
39
'getSingleResult ' => 0 ,
36
40
];
37
41
42
+ private const METHOD_HYDRATION_MODE = [
43
+ 'getArrayResult ' => AbstractQuery::HYDRATE_ARRAY ,
44
+ 'getScalarResult ' => AbstractQuery::HYDRATE_SCALAR ,
45
+ 'getSingleColumnResult ' => AbstractQuery::HYDRATE_SCALAR_COLUMN ,
46
+ 'getSingleScalarResult ' => AbstractQuery::HYDRATE_SINGLE_SCALAR ,
47
+ ];
48
+
38
49
public function getClass (): string
39
50
{
40
51
return AbstractQuery::class;
41
52
}
42
53
43
54
public function isMethodSupported (MethodReflection $ methodReflection ): bool
44
55
{
45
- return isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodReflection ->getName ()]);
56
+ return isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodReflection ->getName ()])
57
+ || isset (self ::METHOD_HYDRATION_MODE [$ methodReflection ->getName ()]);
46
58
}
47
59
48
60
public function getTypeFromMethodCall (
@@ -53,21 +65,23 @@ public function getTypeFromMethodCall(
53
65
{
54
66
$ methodName = $ methodReflection ->getName ();
55
67
56
- if (!isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodName ])) {
57
- throw new ShouldNotHappenException ();
58
- }
59
-
60
- $ argIndex = self ::METHOD_HYDRATION_MODE_ARG [$ methodName ];
61
- $ args = $ methodCall ->getArgs ();
68
+ if (isset (self ::METHOD_HYDRATION_MODE [$ methodName ])) {
69
+ $ hydrationMode = new ConstantIntegerType (self ::METHOD_HYDRATION_MODE [$ methodName ]);
70
+ } elseif (isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodName ])) {
71
+ $ argIndex = self ::METHOD_HYDRATION_MODE_ARG [$ methodName ];
72
+ $ args = $ methodCall ->getArgs ();
62
73
63
- if (isset ($ args [$ argIndex ])) {
64
- $ hydrationMode = $ scope ->getType ($ args [$ argIndex ]->value );
74
+ if (isset ($ args [$ argIndex ])) {
75
+ $ hydrationMode = $ scope ->getType ($ args [$ argIndex ]->value );
76
+ } else {
77
+ $ parametersAcceptor = ParametersAcceptorSelector::selectSingle (
78
+ $ methodReflection ->getVariants ()
79
+ );
80
+ $ parameter = $ parametersAcceptor ->getParameters ()[$ argIndex ];
81
+ $ hydrationMode = $ parameter ->getDefaultValue () ?? new NullType ();
82
+ }
65
83
} else {
66
- $ parametersAcceptor = ParametersAcceptorSelector::selectSingle (
67
- $ methodReflection ->getVariants ()
68
- );
69
- $ parameter = $ parametersAcceptor ->getParameters ()[$ argIndex ];
70
- $ hydrationMode = $ parameter ->getDefaultValue () ?? new NullType ();
84
+ throw new ShouldNotHappenException ();
71
85
}
72
86
73
87
$ queryType = $ scope ->getType ($ methodCall ->var );
@@ -131,12 +145,32 @@ private function getMethodReturnTypeForHydrationMode(
131
145
return $ this ->originalReturnType ($ methodReflection );
132
146
}
133
147
134
- if (!$ this ->isObjectHydrationMode ($ hydrationMode )) {
135
- // We support only HYDRATE_OBJECT. For other hydration modes, we
136
- // return the declared return type of the method.
148
+ if (!$ hydrationMode instanceof ConstantIntegerType) {
137
149
return $ this ->originalReturnType ($ methodReflection );
138
150
}
139
151
152
+ switch ($ hydrationMode ->getValue ()) {
153
+ case AbstractQuery::HYDRATE_OBJECT :
154
+ break ;
155
+ case AbstractQuery::HYDRATE_ARRAY :
156
+ $ queryResultType = $ this ->getArrayHydratedReturnType ($ queryResultType );
157
+ break ;
158
+ case AbstractQuery::HYDRATE_SCALAR :
159
+ $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
160
+ break ;
161
+ case AbstractQuery::HYDRATE_SINGLE_SCALAR :
162
+ $ queryResultType = $ this ->getSingleScalarHydratedReturnType ($ queryResultType );
163
+ break ;
164
+ case AbstractQuery::HYDRATE_SIMPLEOBJECT :
165
+ $ queryResultType = $ this ->getSimpleObjectHydratedReturnType ($ queryResultType );
166
+ break ;
167
+ case AbstractQuery::HYDRATE_SCALAR_COLUMN :
168
+ $ queryResultType = $ this ->getScalarColumnHydratedReturnType ($ queryResultType );
169
+ break ;
170
+ default :
171
+ return $ this ->originalReturnType ($ methodReflection );
172
+ }
173
+
140
174
switch ($ methodReflection ->getName ()) {
141
175
case 'getSingleResult ' :
142
176
return $ queryResultType ;
@@ -161,13 +195,78 @@ private function getMethodReturnTypeForHydrationMode(
161
195
}
162
196
}
163
197
164
- private function isObjectHydrationMode (Type $ type ): bool
198
+ private function getArrayHydratedReturnType (Type $ queryResultType ): Type
165
199
{
166
- if (!$ type instanceof ConstantIntegerType) {
167
- return false ;
200
+ return TypeTraverser::map (
201
+ $ queryResultType ,
202
+ static function (Type $ type , callable $ traverse ): Type {
203
+ $ isObject = (new ObjectWithoutClassType ())->isSuperTypeOf ($ type );
204
+ if ($ isObject ->yes ()) {
205
+ return new ArrayType (new MixedType (), new MixedType ());
206
+ }
207
+ if ($ isObject ->maybe ()) {
208
+ return new MixedType ();
209
+ }
210
+
211
+ return $ traverse ($ type );
212
+ }
213
+ );
214
+ }
215
+
216
+ private function getScalarHydratedReturnType (Type $ queryResultType ): Type
217
+ {
218
+ if (!$ queryResultType instanceof ArrayType) {
219
+ return new ArrayType (new MixedType (), new MixedType ());
220
+ }
221
+
222
+ $ itemType = $ queryResultType ->getItemType ();
223
+ $ hasNoObject = (new ObjectWithoutClassType ())->isSuperTypeOf ($ itemType )->no ();
224
+ $ hasNoArray = $ itemType ->isArray ()->no ();
225
+
226
+ if ($ hasNoArray && $ hasNoObject ) {
227
+ return $ queryResultType ;
228
+ }
229
+
230
+ return new ArrayType (new MixedType (), new MixedType ());
231
+ }
232
+
233
+ private function getSimpleObjectHydratedReturnType (Type $ queryResultType ): Type
234
+ {
235
+ if ((new ObjectWithoutClassType ())->isSuperTypeOf ($ queryResultType )->yes ()) {
236
+ return $ queryResultType ;
237
+ }
238
+
239
+ return new MixedType ();
240
+ }
241
+
242
+ private function getSingleScalarHydratedReturnType (Type $ queryResultType ): Type
243
+ {
244
+ $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
245
+ if (!$ queryResultType instanceof ConstantArrayType) {
246
+ return new ArrayType (new MixedType (), new MixedType ());
247
+ }
248
+
249
+ $ values = $ queryResultType ->getValueTypes ();
250
+ if (count ($ values ) !== 1 ) {
251
+ return new ArrayType (new MixedType (), new MixedType ());
252
+ }
253
+
254
+ return $ queryResultType ;
255
+ }
256
+
257
+ private function getScalarColumnHydratedReturnType (Type $ queryResultType ): Type
258
+ {
259
+ $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
260
+ if (!$ queryResultType instanceof ConstantArrayType) {
261
+ return new MixedType ();
262
+ }
263
+
264
+ $ values = $ queryResultType ->getValueTypes ();
265
+ if (count ($ values ) !== 1 ) {
266
+ return new MixedType ();
168
267
}
169
268
170
- return $ type -> getValue () === AbstractQuery:: HYDRATE_OBJECT ;
269
+ return $ queryResultType -> getFirstIterableValueType () ;
171
270
}
172
271
173
272
private function originalReturnType (MethodReflection $ methodReflection ): Type
0 commit comments