99use PHPStan \Reflection \ParametersAcceptorSelector ;
1010use PHPStan \ShouldNotHappenException ;
1111use PHPStan \Type \ArrayType ;
12+ use PHPStan \Type \Constant \ConstantArrayType ;
1213use PHPStan \Type \Constant \ConstantIntegerType ;
1314use PHPStan \Type \DynamicMethodReturnTypeExtension ;
1415use PHPStan \Type \Generic \GenericObjectType ;
1516use PHPStan \Type \IntegerType ;
1617use PHPStan \Type \IterableType ;
1718use PHPStan \Type \MixedType ;
1819use PHPStan \Type \NullType ;
20+ use PHPStan \Type \ObjectWithoutClassType ;
1921use PHPStan \Type \Type ;
2022use PHPStan \Type \TypeCombinator ;
23+ use PHPStan \Type \TypeTraverser ;
2124use PHPStan \Type \VoidType ;
25+ use function count ;
2226
2327final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
2428{
@@ -109,12 +113,32 @@ private function getMethodReturnTypeForHydrationMode(
109113 return $ this ->originalReturnType ($ methodReflection );
110114 }
111115
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) {
115117 return $ this ->originalReturnType ($ methodReflection );
116118 }
117119
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+
118142 switch ($ methodReflection ->getName ()) {
119143 case 'getSingleResult ' :
120144 return $ queryResultType ;
@@ -133,13 +157,78 @@ private function getMethodReturnTypeForHydrationMode(
133157 }
134158 }
135159
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
137196 {
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 ();
140229 }
141230
142- return $ type -> getValue () === AbstractQuery:: HYDRATE_OBJECT ;
231+ return $ queryResultType -> getFirstIterableValueType () ;
143232 }
144233
145234 private function originalReturnType (MethodReflection $ methodReflection ): Type
0 commit comments