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