Skip to content

Commit 9fac3a7

Browse files
committed
Fix handling of custom Expr objects
1 parent b6a0076 commit 9fac3a7

File tree

5 files changed

+66
-2
lines changed

5 files changed

+66
-2
lines changed

extension.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,10 @@ services:
170170
arguments:
171171
class: Doctrine\ORM\Query\Expr\OrderBy
172172
argumentsProcessor: @doctrineQueryBuilderArgumentsProcessor
173+
-
174+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
175+
tags:
176+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
177+
arguments:
178+
class: Doctrine\ORM\Query\Expr
179+
argumentsProcessor: @doctrineQueryBuilderArgumentsProcessor

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ parameters:
1616
message: '~^Variable method call on Doctrine\\ORM\\QueryBuilder~'
1717
path: */src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php
1818
-
19-
message: '~^Variable method call on Doctrine\\ORM\\Query\\Expr\.$~'
19+
message: '~^Variable method call on object\.$~'
2020
path: */src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php
2121
-
2222
message: '~^Variable property access on PhpParser\\Node\\Stmt\\Declare_\|PhpParser\\Node\\Stmt\\Namespace_\.$~'

src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,18 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
6464
return $defaultReturnType;
6565
}
6666

67-
$exprValue = $queryBuilder->expr()->{$methodReflection->getName()}(...$args);
67+
$calledOnType = $scope->getType($methodCall->var);
68+
if ($calledOnType instanceof ExprType) {
69+
$expr = $calledOnType->getExprObject();
70+
} else {
71+
$expr = $queryBuilder->expr();
72+
}
73+
74+
if (!method_exists($expr, $methodReflection->getName())) {
75+
return $defaultReturnType;
76+
}
77+
78+
$exprValue = $expr->{$methodReflection->getName()}(...$args);
6879
if (is_object($exprValue)) {
6980
return new ExprType(get_class($exprValue), $exprValue);
7081
}

tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Rules\Doctrine\ORM;
44

5+
use Doctrine\ORM\Query\Expr;
56
use Doctrine\ORM\Query\Expr\Base;
67
use Doctrine\ORM\Query\Expr\OrderBy;
78
use PHPStan\DependencyInjection\Container;
@@ -102,6 +103,10 @@ public function testRule(): void
102103
'QueryBuilder: [Semantical Error] line 0, col 60 near \'nonexistent =\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named nonexistent',
103104
251,
104105
],
106+
[
107+
"QueryBuilder: [Syntax Error] line 0, col -1: Error: Expected =, <, <=, <>, >, >=, !=, got end of string.\nDQL: SELECT e FROM PHPStan\Rules\Doctrine\ORM\MyEntity e WHERE foo",
108+
281,
109+
],
105110
]);
106111
}
107112

@@ -210,6 +215,7 @@ public function getDynamicStaticMethodReturnTypeExtensions(): array
210215
return [
211216
new NewExprDynamicReturnTypeExtension($argumentsProcessor, OrderBy::class),
212217
new NewExprDynamicReturnTypeExtension($argumentsProcessor, Base::class),
218+
new NewExprDynamicReturnTypeExtension($argumentsProcessor, Expr::class),
213219
];
214220
}
215221

tests/Rules/Doctrine/ORM/data/query-builder-dql.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,44 @@ public function anotherWeirdTypeSpecifyingExtensionProblem(): void
261261
$queryBuilder->getQuery();
262262
}
263263

264+
public function qbCustomExprMethod(): void
265+
{
266+
$expr = new CustomExpr();
267+
$queryBuilder = $this->entityManager->createQueryBuilder();
268+
$queryBuilder->select('e')
269+
->from(MyEntity::class, 'e')
270+
->andWhere($expr->correct());
271+
$queryBuilder->getQuery();
272+
}
273+
274+
public function qbCustomExprMethodSyntaxError(): void
275+
{
276+
$expr = new CustomExpr();
277+
$queryBuilder = $this->entityManager->createQueryBuilder();
278+
$queryBuilder->select('e')
279+
->from(MyEntity::class, 'e')
280+
->andWhere($expr->syntaxError());
281+
$queryBuilder->getQuery();
282+
}
283+
284+
}
285+
286+
class CustomExpr extends \Doctrine\ORM\Query\Expr
287+
{
288+
289+
public function __construct()
290+
{
291+
// necessary so that NewExprDynamicReturnTypeExtension works
292+
}
293+
294+
public function syntaxError(): string
295+
{
296+
return 'foo';
297+
}
298+
299+
public function correct(): string
300+
{
301+
return 'e.id = 1';
302+
}
303+
264304
}

0 commit comments

Comments
 (0)