88use PHPStan \Reflection \MethodReflection ;
99use PHPStan \Reflection \ParametersAcceptorSelector ;
1010use PHPStan \ShouldNotHappenException ;
11- use PHPStan \Type \Accessory \AccessoryArrayListType ;
12- use PHPStan \Type \ArrayType ;
13- use PHPStan \Type \BenevolentUnionType ;
1411use PHPStan \Type \Constant \ConstantIntegerType ;
12+ use PHPStan \Type \Doctrine \HydrationModeReturnTypeResolver ;
1513use PHPStan \Type \Doctrine \ObjectMetadataResolver ;
1614use PHPStan \Type \DynamicMethodReturnTypeExtension ;
17- use PHPStan \Type \IntegerType ;
18- use PHPStan \Type \IterableType ;
19- use PHPStan \Type \MixedType ;
2015use PHPStan \Type \NullType ;
21- use PHPStan \Type \ObjectWithoutClassType ;
2216use PHPStan \Type \Type ;
23- use PHPStan \Type \TypeCombinator ;
24- use PHPStan \Type \TypeTraverser ;
25- use PHPStan \Type \TypeUtils ;
26- use PHPStan \Type \TypeWithClassName ;
27- use PHPStan \Type \VoidType ;
2817
2918final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
3019{
@@ -46,11 +35,16 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
4635 /** @var ObjectMetadataResolver */
4736 private $ objectMetadataResolver ;
4837
38+ /** @var HydrationModeReturnTypeResolver */
39+ private $ hydrationModeReturnTypeResolver ;
40+
4941 public function __construct (
50- ObjectMetadataResolver $ objectMetadataResolver
42+ ObjectMetadataResolver $ objectMetadataResolver ,
43+ HydrationModeReturnTypeResolver $ hydrationModeReturnTypeResolver
5144 )
5245 {
5346 $ this ->objectMetadataResolver = $ objectMetadataResolver ;
47+ $ this ->hydrationModeReturnTypeResolver = $ hydrationModeReturnTypeResolver ;
5448 }
5549
5650 public function getClass (): string
@@ -93,136 +87,17 @@ public function getTypeFromMethodCall(
9387
9488 $ queryType = $ scope ->getType ($ methodCall ->var );
9589
96- return $ this ->getMethodReturnTypeForHydrationMode (
97- $ methodReflection ,
98- $ hydrationMode ,
99- $ queryType ->getTemplateType (AbstractQuery::class, 'TKey ' ),
100- $ queryType ->getTemplateType (AbstractQuery::class, 'TResult ' )
101- );
102- }
103-
104- private function getMethodReturnTypeForHydrationMode (
105- MethodReflection $ methodReflection ,
106- Type $ hydrationMode ,
107- Type $ queryKeyType ,
108- Type $ queryResultType
109- ): ?Type
110- {
111- $ isVoidType = (new VoidType ())->isSuperTypeOf ($ queryResultType );
112-
113- if ($ isVoidType ->yes ()) {
114- // A void query result type indicates an UPDATE or DELETE query.
115- // In this case all methods return the number of affected rows.
116- return new IntegerType ();
117- }
118-
119- if ($ isVoidType ->maybe ()) {
120- // We can't be sure what the query type is, so we return the
121- // declared return type of the method.
122- return null ;
123- }
124-
12590 if (!$ hydrationMode instanceof ConstantIntegerType) {
12691 return null ;
12792 }
12893
129- switch ($ hydrationMode ->getValue ()) {
130- case AbstractQuery::HYDRATE_OBJECT :
131- break ;
132- case AbstractQuery::HYDRATE_ARRAY :
133- $ queryResultType = $ this ->getArrayHydratedReturnType ($ queryResultType );
134- break ;
135- case AbstractQuery::HYDRATE_SIMPLEOBJECT :
136- $ queryResultType = $ this ->getSimpleObjectHydratedReturnType ($ queryResultType );
137- break ;
138- default :
139- return null ;
140- }
141-
142- if ($ queryResultType === null ) {
143- return null ;
144- }
145-
146- switch ($ methodReflection ->getName ()) {
147- case 'getSingleResult ' :
148- return $ queryResultType ;
149- case 'getOneOrNullResult ' :
150- $ nullableQueryResultType = TypeCombinator::addNull ($ queryResultType );
151- if ($ queryResultType instanceof BenevolentUnionType) {
152- $ nullableQueryResultType = TypeUtils::toBenevolentUnion ($ nullableQueryResultType );
153- }
154-
155- return $ nullableQueryResultType ;
156- case 'toIterable ' :
157- return new IterableType (
158- $ queryKeyType ->isNull ()->yes () ? new IntegerType () : $ queryKeyType ,
159- $ queryResultType
160- );
161- default :
162- if ($ queryKeyType ->isNull ()->yes ()) {
163- return AccessoryArrayListType::intersectWith (new ArrayType (
164- new IntegerType (),
165- $ queryResultType
166- ));
167- }
168- return new ArrayType (
169- $ queryKeyType ,
170- $ queryResultType
171- );
172- }
173- }
174-
175- /**
176- * When we're array-hydrating object, we're not sure of the shape of the array.
177- * We could return `new ArrayTyp(new MixedType(), new MixedType())`
178- * but the lack of precision in the array keys/values would give false positive.
179- *
180- * @see https://github.com/phpstan/phpstan-doctrine/pull/412#issuecomment-1497092934
181- */
182- private function getArrayHydratedReturnType (Type $ queryResultType ): ?Type
183- {
184- $ objectManager = $ this ->objectMetadataResolver ->getObjectManager ();
185-
186- $ mixedFound = false ;
187- $ queryResultType = TypeTraverser::map (
188- $ queryResultType ,
189- static function (Type $ type , callable $ traverse ) use ($ objectManager , &$ mixedFound ): Type {
190- $ isObject = (new ObjectWithoutClassType ())->isSuperTypeOf ($ type );
191- if ($ isObject ->no ()) {
192- return $ traverse ($ type );
193- }
194- if (
195- $ isObject ->maybe ()
196- || !$ type instanceof TypeWithClassName
197- || $ objectManager === null
198- ) {
199- $ mixedFound = true ;
200-
201- return new MixedType ();
202- }
203-
204- /** @var class-string $className */
205- $ className = $ type ->getClassName ();
206- if (!$ objectManager ->getMetadataFactory ()->hasMetadataFor ($ className )) {
207- return $ traverse ($ type );
208- }
209-
210- $ mixedFound = true ;
211-
212- return new MixedType ();
213- }
94+ return $ this ->hydrationModeReturnTypeResolver ->getMethodReturnTypeForHydrationMode (
95+ $ methodReflection ->getName (),
96+ $ hydrationMode ->getValue (),
97+ $ queryType ->getTemplateType (AbstractQuery::class, 'TKey ' ),
98+ $ queryType ->getTemplateType (AbstractQuery::class, 'TResult ' ),
99+ $ this ->objectMetadataResolver ->getObjectManager ()
214100 );
215-
216- return $ mixedFound ? null : $ queryResultType ;
217- }
218-
219- private function getSimpleObjectHydratedReturnType (Type $ queryResultType ): ?Type
220- {
221- if ((new ObjectWithoutClassType ())->isSuperTypeOf ($ queryResultType )->yes ()) {
222- return $ queryResultType ;
223- }
224-
225- return null ;
226101 }
227102
228103}
0 commit comments