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 ;
2225
2326final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -109,12 +112,32 @@ private function getMethodReturnTypeForHydrationMode(
109112 return $ this ->originalReturnType ($ methodReflection );
110113 }
111114
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) {
115116 return $ this ->originalReturnType ($ methodReflection );
116117 }
117118
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+
118141 switch ($ methodReflection ->getName ()) {
119142 case 'getSingleResult ' :
120143 return $ queryResultType ;
@@ -133,13 +156,78 @@ private function getMethodReturnTypeForHydrationMode(
133156 }
134157 }
135158
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
137195 {
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 ();
140228 }
141229
142- return $ type -> getValue () === AbstractQuery:: HYDRATE_OBJECT ;
230+ return $ queryResultType -> getFirstIterableValueType () ;
143231 }
144232
145233 private function originalReturnType (MethodReflection $ methodReflection ): Type
0 commit comments