1010use PHPStan \ShouldNotHappenException ;
1111use PHPStan \Type \Accessory \AccessoryArrayListType ;
1212use PHPStan \Type \ArrayType ;
13- use PHPStan \Type \BenevolentUnionType ;
1413use PHPStan \Type \Constant \ConstantIntegerType ;
15- use PHPStan \Type \Doctrine \ObjectMetadataResolver ;
1614use PHPStan \Type \DynamicMethodReturnTypeExtension ;
1715use PHPStan \Type \IntegerType ;
1816use PHPStan \Type \IterableType ;
19- use PHPStan \Type \MixedType ;
2017use PHPStan \Type \NullType ;
21- use PHPStan \Type \ObjectWithoutClassType ;
2218use PHPStan \Type \Type ;
2319use PHPStan \Type \TypeCombinator ;
24- use PHPStan \Type \TypeTraverser ;
25- use PHPStan \Type \TypeUtils ;
26- use PHPStan \Type \TypeWithClassName ;
2720use PHPStan \Type \VoidType ;
28- use function count ;
2921
3022final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
3123{
@@ -40,32 +32,14 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
4032 'getSingleResult ' => 0 ,
4133 ];
4234
43- private const METHOD_HYDRATION_MODE = [
44- 'getArrayResult ' => AbstractQuery::HYDRATE_ARRAY ,
45- 'getScalarResult ' => AbstractQuery::HYDRATE_SCALAR ,
46- 'getSingleColumnResult ' => AbstractQuery::HYDRATE_SCALAR_COLUMN ,
47- 'getSingleScalarResult ' => AbstractQuery::HYDRATE_SINGLE_SCALAR ,
48- ];
49-
50- /** @var ObjectMetadataResolver */
51- private $ objectMetadataResolver ;
52-
53- public function __construct (
54- ObjectMetadataResolver $ objectMetadataResolver
55- )
56- {
57- $ this ->objectMetadataResolver = $ objectMetadataResolver ;
58- }
59-
6035 public function getClass (): string
6136 {
6237 return AbstractQuery::class;
6338 }
6439
6540 public function isMethodSupported (MethodReflection $ methodReflection ): bool
6641 {
67- return isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodReflection ->getName ()])
68- || isset (self ::METHOD_HYDRATION_MODE [$ methodReflection ->getName ()]);
42+ return isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodReflection ->getName ()]);
6943 }
7044
7145 public function getTypeFromMethodCall (
@@ -76,23 +50,21 @@ public function getTypeFromMethodCall(
7650 {
7751 $ methodName = $ methodReflection ->getName ();
7852
79- if (isset (self ::METHOD_HYDRATION_MODE [$ methodName ])) {
80- $ hydrationMode = new ConstantIntegerType (self ::METHOD_HYDRATION_MODE [$ methodName ]);
81- } elseif (isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodName ])) {
82- $ argIndex = self ::METHOD_HYDRATION_MODE_ARG [$ methodName ];
83- $ args = $ methodCall ->getArgs ();
53+ if (!isset (self ::METHOD_HYDRATION_MODE_ARG [$ methodName ])) {
54+ throw new ShouldNotHappenException ();
55+ }
8456
85- if (isset ($ args [$ argIndex ])) {
86- $ hydrationMode = $ scope ->getType ($ args [$ argIndex ]->value );
87- } else {
88- $ parametersAcceptor = ParametersAcceptorSelector::selectSingle (
89- $ methodReflection ->getVariants ()
90- );
91- $ parameter = $ parametersAcceptor ->getParameters ()[$ argIndex ];
92- $ hydrationMode = $ parameter ->getDefaultValue () ?? new NullType ();
93- }
57+ $ argIndex = self ::METHOD_HYDRATION_MODE_ARG [$ methodName ];
58+ $ args = $ methodCall ->getArgs ();
59+
60+ if (isset ($ args [$ argIndex ])) {
61+ $ hydrationMode = $ scope ->getType ($ args [$ argIndex ]->value );
9462 } else {
95- throw new ShouldNotHappenException ();
63+ $ parametersAcceptor = ParametersAcceptorSelector::selectSingle (
64+ $ methodReflection ->getVariants ()
65+ );
66+ $ parameter = $ parametersAcceptor ->getParameters ()[$ argIndex ];
67+ $ hydrationMode = $ parameter ->getDefaultValue () ?? new NullType ();
9668 }
9769
9870 $ queryType = $ scope ->getType ($ methodCall ->var );
@@ -126,54 +98,23 @@ private function getMethodReturnTypeForHydrationMode(
12698 return $ this ->originalReturnType ($ methodReflection );
12799 }
128100
129- if (!$ hydrationMode instanceof ConstantIntegerType) {
101+ if (!$ this ->isObjectHydrationMode ($ hydrationMode )) {
102+ // We support only HYDRATE_OBJECT. For other hydration modes, we
103+ // return the declared return type of the method.
130104 return $ this ->originalReturnType ($ methodReflection );
131105 }
132106
133- $ singleResult = false ;
134- switch ($ hydrationMode ->getValue ()) {
135- case AbstractQuery::HYDRATE_OBJECT :
136- break ;
137- case AbstractQuery::HYDRATE_ARRAY :
138- $ queryResultType = $ this ->getArrayHydratedReturnType ($ queryResultType );
139- break ;
140- case AbstractQuery::HYDRATE_SCALAR :
141- $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
142- break ;
143- case AbstractQuery::HYDRATE_SINGLE_SCALAR :
144- $ singleResult = true ;
145- $ queryResultType = $ this ->getSingleScalarHydratedReturnType ($ queryResultType );
146- break ;
147- case AbstractQuery::HYDRATE_SIMPLEOBJECT :
148- $ queryResultType = $ this ->getSimpleObjectHydratedReturnType ($ queryResultType );
149- break ;
150- case AbstractQuery::HYDRATE_SCALAR_COLUMN :
151- $ queryResultType = $ this ->getScalarColumnHydratedReturnType ($ queryResultType );
152- break ;
153- default :
154- return $ this ->originalReturnType ($ methodReflection );
155- }
156-
157107 switch ($ methodReflection ->getName ()) {
158108 case 'getSingleResult ' :
159109 return $ queryResultType ;
160110 case 'getOneOrNullResult ' :
161- $ nullableQueryResultType = TypeCombinator::addNull ($ queryResultType );
162- if ($ queryResultType instanceof BenevolentUnionType) {
163- $ nullableQueryResultType = TypeUtils::toBenevolentUnion ($ nullableQueryResultType );
164- }
165-
166- return $ nullableQueryResultType ;
111+ return TypeCombinator::addNull ($ queryResultType );
167112 case 'toIterable ' :
168113 return new IterableType (
169114 $ queryKeyType ->isNull ()->yes () ? new IntegerType () : $ queryKeyType ,
170115 $ queryResultType
171116 );
172117 default :
173- if ($ singleResult ) {
174- return $ queryResultType ;
175- }
176-
177118 if ($ queryKeyType ->isNull ()->yes ()) {
178119 return AccessoryArrayListType::intersectWith (new ArrayType (
179120 new IntegerType (),
@@ -187,104 +128,13 @@ private function getMethodReturnTypeForHydrationMode(
187128 }
188129 }
189130
190- private function getArrayHydratedReturnType (Type $ queryResultType ): Type
191- {
192- $ objectManager = $ this ->objectMetadataResolver ->getObjectManager ();
193-
194- return TypeTraverser::map (
195- $ queryResultType ,
196- static function (Type $ type , callable $ traverse ) use ($ objectManager ): Type {
197- $ isObject = (new ObjectWithoutClassType ())->isSuperTypeOf ($ type );
198- if ($ isObject ->no ()) {
199- return $ traverse ($ type );
200- }
201- if (
202- $ isObject ->maybe ()
203- || !$ type instanceof TypeWithClassName
204- || $ objectManager === null
205- ) {
206- return new MixedType ();
207- }
208-
209- if (!$ objectManager ->getMetadataFactory ()->hasMetadataFor ($ type ->getClassName ())) {
210- return $ traverse ($ type );
211- }
212-
213- // We could return `new ArrayTyp(new MixedType(), new MixedType())`
214- // but the lack of precision in the array keys/values would give false positive
215- // @see https://github.com/phpstan/phpstan-doctrine/pull/412#issuecomment-1497092934
216- return new MixedType ();
217- }
218- );
219- }
220-
221- private function getScalarHydratedReturnType (Type $ queryResultType ): Type
222- {
223- if (!$ queryResultType ->isArray ()->yes ()) {
224- return new ArrayType (new MixedType (), new MixedType ());
225- }
226-
227- foreach ($ queryResultType ->getArrays () as $ arrayType ) {
228- $ itemType = $ arrayType ->getItemType ();
229-
230- if (
231- !(new ObjectWithoutClassType ())->isSuperTypeOf ($ itemType )->no ()
232- || !$ itemType ->isArray ()->no ()
233- ) {
234- return new ArrayType (new MixedType (), new MixedType ());
235- }
236- }
237-
238- return $ queryResultType ;
239- }
240-
241- private function getSimpleObjectHydratedReturnType (Type $ queryResultType ): Type
242- {
243- if ((new ObjectWithoutClassType ())->isSuperTypeOf ($ queryResultType )->yes ()) {
244- return $ queryResultType ;
245- }
246-
247- return new MixedType ();
248- }
249-
250- private function getSingleScalarHydratedReturnType (Type $ queryResultType ): Type
131+ private function isObjectHydrationMode (Type $ type ): bool
251132 {
252- $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
253- if (!$ queryResultType ->isConstantArray ()->yes ()) {
254- return new MixedType ();
255- }
256-
257- $ types = [];
258- foreach ($ queryResultType ->getConstantArrays () as $ constantArrayType ) {
259- $ values = $ constantArrayType ->getValueTypes ();
260- if (count ($ values ) !== 1 ) {
261- return new MixedType ();
262- }
263-
264- $ types [] = $ constantArrayType ->getFirstIterableValueType ();
265- }
266-
267- return TypeCombinator::union (...$ types );
268- }
269-
270- private function getScalarColumnHydratedReturnType (Type $ queryResultType ): Type
271- {
272- $ queryResultType = $ this ->getScalarHydratedReturnType ($ queryResultType );
273- if (!$ queryResultType ->isConstantArray ()->yes ()) {
274- return new MixedType ();
275- }
276-
277- $ types = [];
278- foreach ($ queryResultType ->getConstantArrays () as $ constantArrayType ) {
279- $ values = $ constantArrayType ->getValueTypes ();
280- if (count ($ values ) !== 1 ) {
281- return new MixedType ();
282- }
283-
284- $ types [] = $ constantArrayType ->getFirstIterableValueType ();
133+ if (!$ type instanceof ConstantIntegerType) {
134+ return false ;
285135 }
286136
287- return TypeCombinator:: union (... $ types ) ;
137+ return $ type -> getValue () === AbstractQuery:: HYDRATE_OBJECT ;
288138 }
289139
290140 private function originalReturnType (MethodReflection $ methodReflection ): Type
0 commit comments