Skip to content

Commit ebaa9eb

Browse files
mitelgondrejmirtes
authored andcommitted
Introduce a DynamicMethodReturnTypeExtension for the DBAL QueryBuilder execute method
fixes #117
1 parent 723ed14 commit ebaa9eb

File tree

6 files changed

+174
-0
lines changed

6 files changed

+174
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"doctrine/annotations": "^1.11.0",
2121
"doctrine/collections": "^1.0",
2222
"doctrine/common": "^2.7 || ^3.0",
23+
"doctrine/dbal": "^2.11.0",
2324
"doctrine/mongodb-odm": "^1.3 || ^2.1",
2425
"doctrine/orm": "^2.5",
2526
"doctrine/persistence": "^1.1 || ^2.0",

extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ services:
212212
arguments:
213213
class: Doctrine\ORM\Query\Expr
214214
argumentsProcessor: @doctrineQueryBuilderArgumentsProcessor
215+
-
216+
class: PHPStan\Type\Doctrine\DBAL\QueryBuilder\QueryBuilderExecuteMethodExtension
217+
tags:
218+
- phpstan.broker.dynamicMethodReturnTypeExtension
215219

216220
# Type descriptors
217221
-
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\DBAL\QueryBuilder;
4+
5+
use Doctrine\DBAL\Driver\ResultStatement;
6+
use Doctrine\DBAL\Query\QueryBuilder;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PhpParser\Node\Identifier;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\MethodReflection;
11+
use PHPStan\Reflection\ParametersAcceptorSelector;
12+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
13+
use PHPStan\Type\ObjectType;
14+
use PHPStan\Type\Type;
15+
16+
class QueryBuilderExecuteMethodExtension implements DynamicMethodReturnTypeExtension
17+
{
18+
19+
public function getClass(): string
20+
{
21+
return QueryBuilder::class;
22+
}
23+
24+
public function isMethodSupported(MethodReflection $methodReflection): bool
25+
{
26+
return $methodReflection->getName() === 'execute';
27+
}
28+
29+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
30+
{
31+
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
32+
33+
$queryBuilderType = new ObjectType(QueryBuilder::class);
34+
$var = $methodCall->var;
35+
while ($var instanceof MethodCall) {
36+
$varType = $scope->getType($var->var);
37+
if (!$queryBuilderType->isSuperTypeOf($varType)->yes()) {
38+
return $defaultReturnType;
39+
}
40+
41+
$nameObject = $var->name;
42+
if (!($nameObject instanceof Identifier)) {
43+
return $defaultReturnType;
44+
}
45+
46+
$name = $nameObject->toString();
47+
if ($name === 'select' || $name === 'addSelect') {
48+
return new ObjectType(ResultStatement::class);
49+
}
50+
51+
$var = $var->var;
52+
}
53+
54+
return $defaultReturnType;
55+
}
56+
57+
}

tests/DoctrineIntegration/ORM/EntityManagerIntegrationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function dataTopics(): array
1818
['entityManagerMergeReturn'],
1919
['customRepositoryUsage'],
2020
['queryBuilder'],
21+
['dbalQueryBuilderExecuteDynamicReturn'],
2122
];
2223
}
2324

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{
3+
"message": "Cannot call method fetchAll() on Doctrine\\DBAL\\Driver\\ResultStatement|int.",
4+
"line": 43,
5+
"ignorable": true
6+
},
7+
{
8+
"message": "Cannot call method fetchAll() on Doctrine\\DBAL\\Driver\\ResultStatement|int.",
9+
"line": 60,
10+
"ignorable": true
11+
},
12+
{
13+
"message": "Cannot call method fetchAll() on Doctrine\\DBAL\\Driver\\ResultStatement|int.",
14+
"line": 76,
15+
"ignorable": true
16+
},
17+
{
18+
"message": "Cannot call method fetchAll() on Doctrine\\DBAL\\Driver\\ResultStatement|int.",
19+
"line": 87,
20+
"ignorable": true
21+
}
22+
]
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PHPStan\DoctrineIntegration\ORM\DbalQueryBuilderExecuteDynamicReturn;
4+
5+
use Doctrine\DBAL\Connection;
6+
use Doctrine\DBAL\Query\QueryBuilder;
7+
8+
class Example
9+
{
10+
/** @var Connection */
11+
private $connection;
12+
13+
public function __construct(Connection $connection)
14+
{
15+
$this->connection = $connection;
16+
}
17+
18+
/**
19+
* @return mixed[]
20+
*/
21+
public function testCaseOne(int $userId): array
22+
{
23+
return $this->connection->createQueryBuilder()
24+
->select('*')
25+
->from('user')
26+
->where('user.id = :id')
27+
->setParameter('id', $userId)
28+
->execute()
29+
->fetchAll();
30+
}
31+
32+
/**
33+
* @return mixed[]
34+
*/
35+
public function testCaseTwo(int $userId): array
36+
{
37+
$qb = $this->connection->createQueryBuilder();
38+
$qb->select('*');
39+
$qb->from('user');
40+
$qb->where('user.id = :id');
41+
$qb->setParameter('id', $userId);
42+
43+
return $qb->execute()->fetchAll();
44+
}
45+
46+
/**
47+
* @return mixed[]
48+
*/
49+
public function testCaseThree(?int $userId = null): array
50+
{
51+
$qb = $this->connection->createQueryBuilder();
52+
$qb->select('*');
53+
$qb->from('user');
54+
55+
if ($userId !== null) {
56+
$qb->where('user.id = :id');
57+
$qb->setParameter('id', $userId);
58+
}
59+
60+
return $qb->execute()->fetchAll();
61+
}
62+
63+
/**
64+
* @return mixed[]
65+
*/
66+
public function testCaseFourA(?int $userId = null): array
67+
{
68+
$qb = $this->connection->createQueryBuilder();
69+
$qb->select('*');
70+
$qb->from('user');
71+
72+
if ($userId !== null) {
73+
return $this->testCaseFourB($qb, $userId);
74+
}
75+
76+
return $qb->execute()->fetchAll();
77+
}
78+
79+
/**
80+
* @return mixed[]
81+
*/
82+
private function testCaseFourB(QueryBuilder $qb, int $userId): array
83+
{
84+
$qb->where('user.id = :id');
85+
$qb->setParameter('id', $userId);
86+
87+
return $qb->execute()->fetchAll();
88+
}
89+
}

0 commit comments

Comments
 (0)