Skip to content

Commit 6865aaa

Browse files
committed
Support for $qb->expr()
1 parent a64eb7b commit 6865aaa

File tree

6 files changed

+103
-2
lines changed

6 files changed

+103
-2
lines changed

extension.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ services:
7777
class: PHPStan\PhpDoc\Doctrine\EntityRepositoryTypeNodeResolverExtension
7878
tags:
7979
- phpstan.phpDoc.typeNodeResolverExtension
80+
-
81+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\ExpressionBuilderDynamicReturnTypeExtension
82+
arguments:
83+
argumentsProcessor: @argumentsProcessor
84+
tags:
85+
- phpstan.broker.dynamicMethodReturnTypeExtension
8086

8187
argumentsProcessor:
8288
class: PHPStan\Type\Doctrine\ArgumentsProcessor

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ parameters:
1414
-
1515
message: '~^Variable method call on Doctrine\\ORM\\QueryBuilder\.$~'
1616
path: */src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php
17+
-
18+
message: '~^Variable method call on Doctrine\\ORM\\Query\\Expr\.$~'
19+
path: */src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php

src/Type/Doctrine/ArgumentsProcessor.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ public function processArgs(
3333
$args[] = $value->getExprObject();
3434
continue;
3535
}
36-
// todo $qb->expr() support
3736
if ($value instanceof ConstantArrayType) {
3837
$array = [];
3938
foreach ($value->getKeyTypes() as $i => $keyType) {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\QueryBuilder\Expr;
4+
5+
use Doctrine\ORM\Query\Expr;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Rules\Doctrine\ORM\DynamicQueryBuilderArgumentException;
11+
use PHPStan\Type\Doctrine\ArgumentsProcessor;
12+
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
13+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
14+
use PHPStan\Type\Type;
15+
16+
class ExpressionBuilderDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
17+
{
18+
19+
/** @var ObjectMetadataResolver */
20+
private $objectMetadataResolver;
21+
22+
/** @var \PHPStan\Type\Doctrine\ArgumentsProcessor */
23+
private $argumentsProcessor;
24+
25+
public function __construct(
26+
ObjectMetadataResolver $objectMetadataResolver,
27+
ArgumentsProcessor $argumentsProcessor
28+
)
29+
{
30+
$this->objectMetadataResolver = $objectMetadataResolver;
31+
$this->argumentsProcessor = $argumentsProcessor;
32+
}
33+
34+
public function getClass(): string
35+
{
36+
return Expr::class;
37+
}
38+
39+
public function isMethodSupported(MethodReflection $methodReflection): bool
40+
{
41+
return true;
42+
}
43+
44+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
45+
{
46+
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
47+
48+
$objectManager = $this->objectMetadataResolver->getObjectManager();
49+
if ($objectManager === null) {
50+
return $defaultReturnType;
51+
}
52+
$entityManagerInterface = 'Doctrine\ORM\EntityManagerInterface';
53+
if (!$objectManager instanceof $entityManagerInterface) {
54+
return $defaultReturnType;
55+
}
56+
57+
/** @var \Doctrine\ORM\EntityManagerInterface $objectManager */
58+
$objectManager = $objectManager;
59+
60+
$queryBuilder = $objectManager->createQueryBuilder();
61+
62+
try {
63+
$args = $this->argumentsProcessor->processArgs($scope, $methodReflection->getName(), $methodCall->args);
64+
} catch (DynamicQueryBuilderArgumentException $e) {
65+
return $defaultReturnType;
66+
}
67+
68+
$exprObject = $queryBuilder->expr()->{$methodReflection->getName()}(...$args);
69+
70+
return new ExprType(get_class($exprObject), $exprObject);
71+
}
72+
73+
}

tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
1111
use PHPStan\Type\Doctrine\Query\QueryGetDqlDynamicReturnTypeExtension;
1212
use PHPStan\Type\Doctrine\QueryBuilder\CreateQueryBuilderDynamicReturnTypeExtension;
13+
use PHPStan\Type\Doctrine\QueryBuilder\Expr\ExpressionBuilderDynamicReturnTypeExtension;
1314
use PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension;
1415
use PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetQueryDynamicReturnTypeExtension;
1516
use PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderMethodDynamicReturnTypeExtension;
@@ -74,6 +75,10 @@ public function testRule(): void
7475
'QueryBuilder: [Semantical Error] line 0, col 60 near \'transient = 1\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named transient',
7576
170,
7677
],
78+
[
79+
'QueryBuilder: [Semantical Error] line 0, col 72 near \'nickname LIKE\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named nickname',
80+
194,
81+
],
7782
]);
7883
}
7984

@@ -82,11 +87,14 @@ public function testRule(): void
8287
*/
8388
public function getDynamicMethodReturnTypeExtensions(): array
8489
{
90+
$objectMetadataResolver = new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', null);
91+
$argumentsProcessor = new ArgumentsProcessor();
8592
return [
8693
new CreateQueryBuilderDynamicReturnTypeExtension(null),
8794
new QueryBuilderMethodDynamicReturnTypeExtension(null),
88-
new QueryBuilderGetQueryDynamicReturnTypeExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php', null), new ArgumentsProcessor(), null),
95+
new QueryBuilderGetQueryDynamicReturnTypeExtension($objectMetadataResolver, $argumentsProcessor, null),
8996
new QueryGetDqlDynamicReturnTypeExtension(),
97+
new ExpressionBuilderDynamicReturnTypeExtension($objectMetadataResolver, $argumentsProcessor),
9098
];
9199
}
92100

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,16 @@ public function addNewExprBaseCorrect(): void
188188
->getQuery();
189189
}
190190

191+
public function qbExpr(): void
192+
{
193+
$queryBuilder = $this->entityManager->createQueryBuilder();
194+
$queryBuilder->select('e')
195+
->from(MyEntity::class, 'e')
196+
->add('where', $queryBuilder->expr()->orX(
197+
$queryBuilder->expr()->eq('e.id', '1'),
198+
$queryBuilder->expr()->like('e.nickname', '\'nick\'')
199+
))
200+
->getQuery();
201+
}
202+
191203
}

0 commit comments

Comments
 (0)