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 ;
22
25
23
26
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -109,12 +112,32 @@ private function getMethodReturnTypeForHydrationMode(
109
112
return $ this ->originalReturnType ($ methodReflection );
110
113
}
111
114
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) {
115
116
return $ this ->originalReturnType ($ methodReflection );
116
117
}
117
118
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
+
118
141
switch ($ methodReflection ->getName ()) {
119
142
case 'getSingleResult ' :
120
143
return $ queryResultType ;
@@ -133,13 +156,78 @@ private function getMethodReturnTypeForHydrationMode(
133
156
}
134
157
}
135
158
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
137
195
{
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 ();
140
228
}
141
229
142
- return $ type -> getValue () === AbstractQuery:: HYDRATE_OBJECT ;
230
+ return $ queryResultType -> getFirstIterableValueType () ;
143
231
}
144
232
145
233
private function originalReturnType (MethodReflection $ methodReflection ): Type
0 commit comments