Skip to content

Commit 1635ed0

Browse files
committed
Analyze new Expr() first assigned to a variable
1 parent f6259c4 commit 1635ed0

File tree

6 files changed

+176
-5
lines changed

6 files changed

+176
-5
lines changed

extension.neon

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,48 @@ services:
9191
- phpstan.broker.dynamicMethodReturnTypeExtension
9292
arguments:
9393
managerClass: Doctrine\Common\Persistence\ObjectManager
94+
95+
## NewExprDynamicReturnTypeExtensions
96+
97+
-
98+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
99+
tags:
100+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
101+
arguments:
102+
class: Doctrine\ORM\Query\Expr\Base
103+
-
104+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
105+
tags:
106+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
107+
arguments:
108+
class: Doctrine\ORM\Query\Expr\Comparison
109+
-
110+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
111+
tags:
112+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
113+
arguments:
114+
class: Doctrine\ORM\Query\Expr\From
115+
-
116+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
117+
tags:
118+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
119+
arguments:
120+
class: Doctrine\ORM\Query\Expr\Func
121+
-
122+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
123+
tags:
124+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
125+
arguments:
126+
class: Doctrine\ORM\Query\Expr\Join
127+
-
128+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
129+
tags:
130+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
131+
arguments:
132+
class: Doctrine\ORM\Query\Expr\Math
133+
-
134+
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension
135+
tags:
136+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
137+
arguments:
138+
class: Doctrine\ORM\Query\Expr\OrderBy
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\QueryBuilder\Expr;
4+
5+
use PHPStan\Type\ObjectType;
6+
7+
class ExprType extends ObjectType
8+
{
9+
10+
/** @var \PhpParser\Node\Arg[] */
11+
private $constructorArgs;
12+
13+
/**
14+
* @param string $className
15+
* @param \PhpParser\Node\Arg[] $constructorArgs
16+
*/
17+
public function __construct(string $className, array $constructorArgs)
18+
{
19+
parent::__construct($className);
20+
$this->constructorArgs = $constructorArgs;
21+
}
22+
23+
/**
24+
* @return \PhpParser\Node\Arg[]
25+
*/
26+
public function getConstructorArgs(): array
27+
{
28+
return $this->constructorArgs;
29+
}
30+
31+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\QueryBuilder\Expr;
4+
5+
use PhpParser\Node\Expr\StaticCall;
6+
use PhpParser\Node\Name;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
10+
use PHPStan\Type\Type;
11+
12+
class NewExprDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
13+
{
14+
15+
/** @var string */
16+
private $class;
17+
18+
public function __construct(string $class)
19+
{
20+
$this->class = $class;
21+
}
22+
23+
public function getClass(): string
24+
{
25+
return $this->class;
26+
}
27+
28+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
29+
{
30+
return $methodReflection->getName() === '__construct';
31+
}
32+
33+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
34+
{
35+
if (!$methodCall->class instanceof Name) {
36+
throw new \PHPStan\ShouldNotHappenException();
37+
}
38+
return new ExprType($methodCall->class->toString(), $methodCall->args);
39+
}
40+
41+
}

src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace PHPStan\Type\Doctrine\QueryBuilder;
44

55
use PhpParser\Node\Expr\MethodCall;
6-
use PhpParser\Node\Expr\New_;
76
use PhpParser\Node\Identifier;
87
use PHPStan\Analyser\Scope;
98
use PHPStan\Reflection\MethodReflection;
@@ -13,8 +12,8 @@
1312
use PHPStan\Type\ConstantScalarType;
1413
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
1514
use PHPStan\Type\Doctrine\Query\QueryType;
15+
use PHPStan\Type\Doctrine\QueryBuilder\Expr\ExprType;
1616
use PHPStan\Type\Type;
17-
use PHPStan\Type\TypeWithClassName;
1817
use function in_array;
1918
use function method_exists;
2019
use function strtolower;
@@ -130,12 +129,11 @@ protected function processArgs(Scope $scope, string $methodName, array $methodCa
130129
foreach ($methodCallArgs as $arg) {
131130
$value = $scope->getType($arg->value);
132131
if (
133-
$arg->value instanceof New_
134-
&& $value instanceof TypeWithClassName
132+
$value instanceof ExprType
135133
&& strpos($value->getClassName(), 'Doctrine\ORM\Query\Expr') === 0
136134
) {
137135
$className = $value->getClassName();
138-
$args[] = new $className(...$this->processArgs($scope, '__construct', $arg->value->args));
136+
$args[] = new $className(...$this->processArgs($scope, '__construct', $value->getConstructorArgs()));
139137
continue;
140138
}
141139
// todo $qb->expr() support

tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php

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

33
namespace PHPStan\Rules\Doctrine\ORM;
44

5+
use Doctrine\ORM\Query\Expr\Base;
6+
use Doctrine\ORM\Query\Expr\OrderBy;
57
use PHPStan\Rules\Rule;
68
use PHPStan\Testing\RuleTestCase;
79
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
810
use PHPStan\Type\Doctrine\Query\QueryGetDqlDynamicReturnTypeExtension;
911
use PHPStan\Type\Doctrine\QueryBuilder\CreateQueryBuilderDynamicReturnTypeExtension;
12+
use PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension;
1013
use PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetQueryDynamicReturnTypeExtension;
1114
use PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderMethodDynamicReturnTypeExtension;
1215
use PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderTypeSpecifyingExtension;
@@ -62,6 +65,14 @@ public function testRule(): void
6265
'QueryBuilder: [Semantical Error] line 0, col 78 near \'name ASC\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named name',
6366
139,
6467
],
68+
[
69+
'QueryBuilder: [Semantical Error] line 0, col 78 near \'name ASC\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named name',
70+
160,
71+
],
72+
[
73+
'QueryBuilder: [Semantical Error] line 0, col 60 near \'transient = 1\': Error: Class PHPStan\Rules\Doctrine\ORM\MyEntity has no field or association named transient',
74+
170,
75+
],
6576
]);
6677
}
6778

@@ -88,4 +99,15 @@ protected function getMethodTypeSpecifyingExtensions(): array
8899
];
89100
}
90101

102+
/**
103+
* @return \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[]
104+
*/
105+
public function getDynamicStaticMethodReturnTypeExtensions(): array
106+
{
107+
return [
108+
new NewExprDynamicReturnTypeExtension(OrderBy::class),
109+
new NewExprDynamicReturnTypeExtension(Base::class),
110+
];
111+
}
112+
91113
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,38 @@ public function addNewExprCorrect(): void
154154
->getQuery();
155155
}
156156

157+
public function addNewExprFirstAssignedToVariable(): void
158+
{
159+
$orderBy = new \Doctrine\ORM\Query\Expr\OrderBy('e.name', 'ASC');
160+
$this->entityManager->createQueryBuilder()
161+
->select('e')
162+
->from(MyEntity::class, 'e')
163+
->andWhere('e.id = 1')
164+
->add('orderBy', $orderBy)
165+
->getQuery();
166+
}
167+
168+
public function addNewExprBase(): void
169+
{
170+
$this->entityManager->createQueryBuilder()
171+
->select('e')
172+
->from(MyEntity::class, 'e')
173+
->add('where', new \Doctrine\ORM\Query\Expr\Andx([
174+
'e.transient = 1',
175+
'e.name = \'foo\'',
176+
]))
177+
->getQuery();
178+
}
179+
180+
public function addNewExprBaseCorrect(): void
181+
{
182+
$this->entityManager->createQueryBuilder()
183+
->select('e')
184+
->from(MyEntity::class, 'e')
185+
->add('where', new \Doctrine\ORM\Query\Expr\Andx([
186+
'e.id = 1',
187+
]))
188+
->getQuery();
189+
}
190+
157191
}

0 commit comments

Comments
 (0)