8
8
use PHPStan \Reflection \MethodReflection ;
9
9
use PHPStan \Reflection \ParametersAcceptorSelector ;
10
10
use PHPStan \ShouldNotHappenException ;
11
- use PHPStan \Type \Accessory \AccessoryArrayListType ;
12
- use PHPStan \Type \ArrayType ;
13
- use PHPStan \Type \BenevolentUnionType ;
14
11
use PHPStan \Type \Constant \ConstantIntegerType ;
12
+ use PHPStan \Type \Doctrine \HydrationModeReturnTypeResolver ;
15
13
use PHPStan \Type \Doctrine \ObjectMetadataResolver ;
16
14
use PHPStan \Type \DynamicMethodReturnTypeExtension ;
17
- use PHPStan \Type \IntegerType ;
18
- use PHPStan \Type \IterableType ;
19
- use PHPStan \Type \MixedType ;
20
15
use PHPStan \Type \NullType ;
21
- use PHPStan \Type \ObjectWithoutClassType ;
22
16
use 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 ;
28
17
29
18
final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
30
19
{
@@ -46,11 +35,16 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
46
35
/** @var ObjectMetadataResolver */
47
36
private $ objectMetadataResolver ;
48
37
38
+ /** @var HydrationModeReturnTypeResolver */
39
+ private $ hydrationModeReturnTypeResolver ;
40
+
49
41
public function __construct (
50
- ObjectMetadataResolver $ objectMetadataResolver
42
+ ObjectMetadataResolver $ objectMetadataResolver ,
43
+ HydrationModeReturnTypeResolver $ hydrationModeReturnTypeResolver
51
44
)
52
45
{
53
46
$ this ->objectMetadataResolver = $ objectMetadataResolver ;
47
+ $ this ->hydrationModeReturnTypeResolver = $ hydrationModeReturnTypeResolver ;
54
48
}
55
49
56
50
public function getClass (): string
@@ -93,136 +87,17 @@ public function getTypeFromMethodCall(
93
87
94
88
$ queryType = $ scope ->getType ($ methodCall ->var );
95
89
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
-
125
90
if (!$ hydrationMode instanceof ConstantIntegerType) {
126
91
return null ;
127
92
}
128
93
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 ()
214
100
);
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 ;
226
101
}
227
102
228
103
}
0 commit comments