|
12 | 12 | use PHPStan\Type\ArrayType;
|
13 | 13 | use PHPStan\Type\BenevolentUnionType;
|
14 | 14 | use PHPStan\Type\Constant\ConstantIntegerType;
|
| 15 | +use PHPStan\Type\Doctrine\HydrationModeReturnTypeResolver; |
15 | 16 | use PHPStan\Type\Doctrine\ObjectMetadataResolver;
|
16 | 17 | use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
17 | 18 | use PHPStan\Type\IntegerType;
|
@@ -46,11 +47,16 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
|
46 | 47 | /** @var ObjectMetadataResolver */
|
47 | 48 | private $objectMetadataResolver;
|
48 | 49 |
|
| 50 | + /** @var HydrationModeReturnTypeResolver */ |
| 51 | + private $hydrationModeReturnTypeResolver; |
| 52 | + |
49 | 53 | public function __construct(
|
50 |
| - ObjectMetadataResolver $objectMetadataResolver |
| 54 | + ObjectMetadataResolver $objectMetadataResolver, |
| 55 | + HydrationModeReturnTypeResolver $hydrationModeReturnTypeResolver |
51 | 56 | )
|
52 | 57 | {
|
53 | 58 | $this->objectMetadataResolver = $objectMetadataResolver;
|
| 59 | + $this->hydrationModeReturnTypeResolver = $hydrationModeReturnTypeResolver; |
54 | 60 | }
|
55 | 61 |
|
56 | 62 | public function getClass(): string
|
@@ -93,136 +99,16 @@ public function getTypeFromMethodCall(
|
93 | 99 |
|
94 | 100 | $queryType = $scope->getType($methodCall->var);
|
95 | 101 |
|
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 | 102 | if (!$hydrationMode instanceof ConstantIntegerType) {
|
126 | 103 | return null;
|
127 | 104 | }
|
128 | 105 |
|
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 |
| - } |
| 106 | + return $this->hydrationModeReturnTypeResolver->getMethodReturnTypeForHydrationMode( |
| 107 | + $methodReflection->getName(), |
| 108 | + $hydrationMode->getValue(), |
| 109 | + $queryType->getTemplateType(AbstractQuery::class, 'TKey'), |
| 110 | + $queryType->getTemplateType(AbstractQuery::class, 'TResult') |
214 | 111 | );
|
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 | 112 | }
|
227 | 113 |
|
228 | 114 | }
|
0 commit comments