9
9
use PHPStan \Reflection \ParametersAcceptorSelector ;
10
10
use PHPStan \ShouldNotHappenException ;
11
11
use PHPStan \Type \ArrayType ;
12
+ use PHPStan \Type \Constant \ConstantArrayType ;
12
13
use PHPStan \Type \Constant \ConstantIntegerType ;
13
14
use PHPStan \Type \DynamicMethodReturnTypeExtension ;
14
15
use PHPStan \Type \Generic \GenericObjectType ;
15
16
use PHPStan \Type \IntegerType ;
16
17
use PHPStan \Type \IterableType ;
17
18
use PHPStan \Type \MixedType ;
18
19
use PHPStan \Type \NullType ;
20
+ use PHPStan \Type \ObjectWithoutClassType ;
19
21
use PHPStan \Type \Type ;
20
22
use PHPStan \Type \TypeCombinator ;
23
+ use PHPStan \Type \TypeTraverser ;
21
24
use PHPStan \Type \VoidType ;
25
+ use function count ;
22
26
23
27
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
24
28
{
@@ -33,14 +37,22 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
33
37
'getSingleResult ' => 0 ,
34
38
];
35
39
40
+ private const METHOD_HYDRATION_MODE = [
41
+ 'getArrayResult ' => AbstractQuery::HYDRATE_ARRAY ,
42
+ 'getScalarResult ' => AbstractQuery::HYDRATE_SCALAR ,
43
+ 'getSingleColumnResult ' => AbstractQuery::HYDRATE_SCALAR_COLUMN ,
44
+ 'getSingleScalarResult ' => AbstractQuery::HYDRATE_SINGLE_SCALAR ,
45
+ ];
46
+
36
47
public function getClass (): string
37
48
{
38
49
return AbstractQuery::class;
39
50
}
40
51
41
52
public function isMethodSupported (MethodReflection $ methodReflection ): bool
42
53
{
43
- return isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodReflection ->getName ()]);
54
+ return isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodReflection ->getName ()])
55
+ || isset (self ::METHOD_HYDRATION_MODE [$ methodReflection ->getName ()]);
44
56
}
45
57
46
58
public function getTypeFromMethodCall (
@@ -51,21 +63,23 @@ public function getTypeFromMethodCall(
51
63
{
52
64
$ methodName = $ methodReflection ->getName ();
53
65
54
- if (!isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodName ])) {
55
- throw new ShouldNotHappenException ();
56
- }
57
-
58
- $ argIndex = self ::METHOD_HYDRATION_MODE_ARG [$ methodName ];
59
- $ args = $ methodCall ->getArgs ();
60
-
61
- if (isset ($ args [$ argIndex ])) {
62
- $ hydrationMode = $ scope ->getType ($ args [$ argIndex ]->value );
66
+ if (isset (self ::METHOD_HYDRATION_MODE [$ methodName ])) {
67
+ $ hydrationMode = self ::METHOD_HYDRATION_MODE [$ methodName ];
68
+ } elseif (isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodName ])) {
69
+ $ argIndex = self ::METHOD_HYDRATION_MODE_ARG [$ methodName ];
70
+ $ args = $ methodCall ->getArgs ();
71
+
72
+ if (isset ($ args [$ argIndex ])) {
73
+ $ hydrationMode = $ scope ->getType ($ args [$ argIndex ]->value );
74
+ } else {
75
+ $ parametersAcceptor = ParametersAcceptorSelector::selectSingle (
76
+ $ methodReflection ->getVariants ()
77
+ );
78
+ $ parameter = $ parametersAcceptor ->getParameters ()[$ argIndex ];
79
+ $ hydrationMode = $ parameter ->getDefaultValue () ?? new NullType ();
80
+ }
63
81
} else {
64
- $ parametersAcceptor = ParametersAcceptorSelector::selectSingle (
65
- $ methodReflection ->getVariants ()
66
- );
67
- $ parameter = $ parametersAcceptor ->getParameters ()[$ argIndex ];
68
- $ hydrationMode = $ parameter ->getDefaultValue () ?? new NullType ();
82
+ throw new ShouldNotHappenException ();
69
83
}
70
84
71
85
$ queryType = $ scope ->getType ($ methodCall ->var );
@@ -109,12 +123,32 @@ private function getMethodReturnTypeForHydrationMode(
109
123
return $ this ->originalReturnType ($ methodReflection );
110
124
}
111
125
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.
126
+ if (!$ hydrationMode instanceof ConstantIntegerType) {
115
127
return $ this ->originalReturnType ($ methodReflection );
116
128
}
117
129
130
+ switch ($ hydrationMode ->getValue ()) {
131
+ case AbstractQuery::HYDRATE_OBJECT :
132
+ break ;
133
+ case AbstractQuery::HYDRATE_ARRAY :
134
+ $ queryResultType = $ this ->getArrayHydratedReturnType ($ queryResultType );
135
+ break ;
136
+ case AbstractQuery::HYDRATE_SCALAR :
137
+ $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
138
+ break ;
139
+ case AbstractQuery::HYDRATE_SINGLE_SCALAR :
140
+ $ queryResultType = $ this ->getSingleScalarHydratedReturnType ($ queryResultType );
141
+ break ;
142
+ case AbstractQuery::HYDRATE_SIMPLEOBJECT :
143
+ $ queryResultType = $ this ->getSimpleObjectHydratedReturnType ($ queryResultType );
144
+ break ;
145
+ case AbstractQuery::HYDRATE_SCALAR_COLUMN :
146
+ $ queryResultType = $ this ->getScalarColumnHydratedReturnType ($ queryResultType );
147
+ break ;
148
+ default :
149
+ return $ this ->originalReturnType ($ methodReflection );
150
+ }
151
+
118
152
switch ($ methodReflection ->getName ()) {
119
153
case 'getSingleResult ' :
120
154
return $ queryResultType ;
@@ -133,13 +167,78 @@ private function getMethodReturnTypeForHydrationMode(
133
167
}
134
168
}
135
169
136
- private function isObjectHydrationMode (Type $ type ): bool
170
+ private function getArrayHydratedReturnType (Type $ queryResultType ): Type
171
+ {
172
+ return TypeTraverser::map (
173
+ $ queryResultType ,
174
+ static function (Type $ type , callable $ traverse ): Type {
175
+ $ isObject = (new ObjectWithoutClassType ())->isSuperTypeOf ($ type );
176
+ if ($ isObject ->yes ()) {
177
+ return new ArrayType (new MixedType (), new MixedType ());
178
+ }
179
+ if ($ isObject ->maybe ()) {
180
+ return new MixedType ();
181
+ }
182
+
183
+ return $ traverse ($ type );
184
+ }
185
+ );
186
+ }
187
+
188
+ private function getScalarHydratedReturnType (Type $ queryResultType ): Type
137
189
{
138
- if (!$ type instanceof ConstantIntegerType) {
139
- return false ;
190
+ if (!$ queryResultType instanceof ArrayType) {
191
+ return new ArrayType (new MixedType (), new MixedType ());
192
+ }
193
+
194
+ $ itemType = $ queryResultType ->getItemType ();
195
+ $ hasNoObject = (new ObjectWithoutClassType ())->isSuperTypeOf ($ itemType )->no ();
196
+ $ hasNoArray = $ itemType ->isArray ()->no ();
197
+
198
+ if ($ hasNoArray && $ hasNoObject ) {
199
+ return $ queryResultType ;
200
+ }
201
+
202
+ return new ArrayType (new MixedType (), new MixedType ());
203
+ }
204
+
205
+ private function getSimpleObjectHydratedReturnType (Type $ queryResultType ): Type
206
+ {
207
+ if ((new ObjectWithoutClassType ())->isSuperTypeOf ($ queryResultType )->yes ()) {
208
+ return $ queryResultType ;
209
+ }
210
+
211
+ return new MixedType ();
212
+ }
213
+
214
+ private function getSingleScalarHydratedReturnType (Type $ queryResultType ): Type
215
+ {
216
+ $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
217
+ if (!$ queryResultType instanceof ConstantArrayType) {
218
+ return new ArrayType (new MixedType (), new MixedType ());
219
+ }
220
+
221
+ $ values = $ queryResultType ->getValueTypes ();
222
+ if (count ($ values ) !== 1 ) {
223
+ return new ArrayType (new MixedType (), new MixedType ());
224
+ }
225
+
226
+ return $ queryResultType ;
227
+ }
228
+
229
+ private function getScalarColumnHydratedReturnType (Type $ queryResultType ): Type
230
+ {
231
+ $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
232
+ if (!$ queryResultType instanceof ConstantArrayType) {
233
+ return new MixedType ();
234
+ }
235
+
236
+ $ values = $ queryResultType ->getValueTypes ();
237
+ if (count ($ values ) !== 1 ) {
238
+ return new MixedType ();
140
239
}
141
240
142
- return $ type -> getValue () === AbstractQuery:: HYDRATE_OBJECT ;
241
+ return $ queryResultType -> getFirstIterableValueType () ;
143
242
}
144
243
145
244
private function originalReturnType (MethodReflection $ methodReflection ): Type
0 commit comments