Skip to content

Commit fce74ca

Browse files
janedbalondrejmirtes
authored andcommitted
Infer QueryBuilderType for any method returning QB
1 parent 95959dc commit fce74ca

File tree

4 files changed

+86
-2
lines changed

4 files changed

+86
-2
lines changed

extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ services:
136136
class: PHPStan\Type\Doctrine\Query\QueryGetDqlDynamicReturnTypeExtension
137137
tags:
138138
- phpstan.broker.dynamicMethodReturnTypeExtension
139+
-
140+
class: PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderReturnedDynamicReturnTypeExtension
141+
tags:
142+
- phpstan.broker.dynamicMethodReturnTypeExtension
139143
-
140144
class: PHPStan\Type\Doctrine\CreateQueryDynamicReturnTypeExtension
141145
arguments:

src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,22 @@ public function __construct(bool $descendIntoOtherMethods, ReflectionProvider $r
5454
*/
5555
public function getQueryBuilderTypes(Scope $scope, MethodCall $methodCall): array
5656
{
57-
if (!$this->descendIntoOtherMethods || !$methodCall->var instanceof MethodCall) {
57+
if (!$methodCall->var instanceof MethodCall) {
5858
return [];
5959
}
6060

6161
return $this->findQueryBuilderTypesInCalledMethod($scope, $methodCall->var);
6262
}
63+
6364
/**
6465
* @return QueryBuilderType[]
6566
*/
66-
private function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodCall $methodCall): array
67+
public function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodCall $methodCall): array
6768
{
69+
if (!$this->descendIntoOtherMethods) {
70+
return [];
71+
}
72+
6873
$methodCalledOnType = $scope->getType($methodCall->var);
6974
if (!$methodCall->name instanceof Identifier) {
7075
return [];
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\QueryBuilder;
4+
5+
use Doctrine\ORM\QueryBuilder;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
11+
use PHPStan\Type\MixedType;
12+
use PHPStan\Type\ObjectType;
13+
use PHPStan\Type\Type;
14+
use PHPStan\Type\TypeCombinator;
15+
use function count;
16+
17+
class QueryBuilderReturnedDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
18+
{
19+
20+
/** @var OtherMethodQueryBuilderParser */
21+
private $otherMethodQueryBuilderParser;
22+
23+
public function __construct(
24+
OtherMethodQueryBuilderParser $otherMethodQueryBuilderParser
25+
)
26+
{
27+
$this->otherMethodQueryBuilderParser = $otherMethodQueryBuilderParser;
28+
}
29+
30+
public function getClass(): string
31+
{
32+
return 'QueryResult\CreateQuery\QueryBuilderGetQuery'; // TODO https://github.com/phpstan/phpstan-src/pull/2761
33+
}
34+
35+
public function isMethodSupported(MethodReflection $methodReflection): bool
36+
{
37+
$returnType = ParametersAcceptorSelector::selectSingle(
38+
$methodReflection->getVariants()
39+
)->getReturnType();
40+
41+
if ($returnType instanceof MixedType) {
42+
return false;
43+
}
44+
45+
return (new ObjectType(QueryBuilder::class))->isSuperTypeOf($returnType)->yes();
46+
}
47+
48+
public function getTypeFromMethodCall(
49+
MethodReflection $methodReflection,
50+
MethodCall $methodCall,
51+
Scope $scope
52+
): Type
53+
{
54+
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs(
55+
$scope,
56+
$methodCall->getArgs(),
57+
$methodReflection->getVariants()
58+
)->getReturnType();
59+
60+
$queryBuilderTypes = $this->otherMethodQueryBuilderParser->findQueryBuilderTypesInCalledMethod($scope, $methodCall);
61+
if (count($queryBuilderTypes) === 0) {
62+
return $defaultReturnType;
63+
}
64+
65+
return TypeCombinator::union(...$queryBuilderTypes);
66+
}
67+
68+
}

tests/Type/Doctrine/data/QueryResult/queryBuilderGetQuery.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ public function testQueryTypeIsInferredOnAcrossMethods(EntityManagerInterface $e
242242
assertType('Doctrine\ORM\Query<null, QueryResult\Entities\Many>', $branchingQuery);
243243
}
244244

245+
public function testQueryTypeIsInferredOnAcrossMethodsEvenWhenVariableAssignmentIsUsed(EntityManagerInterface $em): void
246+
{
247+
$queryBuilder = $this->getQueryBuilder($em);
248+
249+
assertType('Doctrine\ORM\Query<null, QueryResult\Entities\Many>', $queryBuilder->getQuery());
250+
}
251+
245252
private function getQueryBuilder(EntityManagerInterface $em): QueryBuilder
246253
{
247254
return $em->createQueryBuilder()

0 commit comments

Comments
 (0)