Skip to content

Commit dc63ecb

Browse files
craigfrancisondrejmirtes
authored andcommitted
Add dynamic literal-string return types for some Expr methods
1 parent 86e74f3 commit dc63ecb

File tree

7 files changed

+180
-19
lines changed

7 files changed

+180
-19
lines changed

extension.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ parameters:
3939
- stubs/ORM/Mapping/ClassMetadata.stub
4040
- stubs/ORM/Mapping/ClassMetadataInfo.stub
4141
- stubs/ORM/ORMException.stub
42+
- stubs/ORM/Query/Expr.stub
4243
- stubs/ORM/Query.stub
4344
- stubs/ORM/Query/Expr/Comparison.stub
4445
- stubs/ORM/Query/Expr/Composite.stub

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
4646

4747
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
4848
{
49-
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
49+
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType();
5050

5151
$objectManager = $this->objectMetadataResolver->getObjectManager();
5252
if ($objectManager === null) {

stubs/ORM/Query/Expr.stub

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Doctrine\ORM\Query;
4+
5+
class Expr
6+
{
7+
8+
/**
9+
* @param string $x
10+
* @return ($x is literal-string ? literal-string&non-empty-string : string)
11+
*/
12+
public function isNull($x)
13+
{
14+
}
15+
16+
/**
17+
* @param string $x
18+
* @return ($x is literal-string ? literal-string&non-empty-string : string)
19+
*/
20+
public function isNotNull($x)
21+
{
22+
}
23+
24+
/**
25+
* @param string $val
26+
* @param string $x
27+
* @param string $y
28+
* @return ($val is literal-string ? ($x is literal-string ? ($y is literal-string ? literal-string&non-empty-string : string) : string) : string)
29+
*/
30+
public function between($val, $x, $y)
31+
{
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\QueryBuilder\Expr;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class ExpressionBuilderDynamicReturnTypeExtensionNoObjectManagerTest extends TypeInferenceTestCase
8+
{
9+
10+
/** @return iterable<mixed> */
11+
public function dataFileAsserts(): iterable
12+
{
13+
yield from $this->gatherAssertTypes(__DIR__ . '/../../data/QueryResult/expressionBuilderGetQueryNoObjectManager.php');
14+
}
15+
16+
/**
17+
* @dataProvider dataFileAsserts
18+
* @param mixed ...$args
19+
*/
20+
public function testFileAsserts(
21+
string $assertType,
22+
string $file,
23+
...$args
24+
): void
25+
{
26+
$this->assertFileAsserts($assertType, $file, ...$args);
27+
}
28+
29+
/** @return string[] */
30+
public static function getAdditionalConfigFiles(): array
31+
{
32+
return [__DIR__ . '/../../data/QueryResult/config-without-object-manager.neon'];
33+
}
34+
35+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
includes:
2+
- ../../../../../extension.neon

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010

1111
class ExpressionBuilderGetQuery
1212
{
13+
private function nonLiteralString(string $value): string {
14+
return $value; // Using the 'string' return type to provide a non `literal-string`, e.g. $_POST['field'];
15+
}
16+
1317
public function isNullLiteralString(EntityManagerInterface $em): void
1418
{
1519
$result = $em->createQueryBuilder()->expr()->isNull('field');
@@ -18,9 +22,9 @@ public function isNullLiteralString(EntityManagerInterface $em): void
1822

1923
public function isNullNonLiteralString(EntityManagerInterface $em): void
2024
{
21-
$field = strtolower('field'); // Non literal-string, e.g. $_POST['field'];
25+
$field = $this->nonLiteralString('field');
2226
$result = $em->createQueryBuilder()->expr()->isNull($field);
23-
assertType("'field IS NULL'", $result);
27+
assertType('string', $result);
2428
}
2529

2630
public function isNotNullLiteralString(EntityManagerInterface $em): void
@@ -31,35 +35,36 @@ public function isNotNullLiteralString(EntityManagerInterface $em): void
3135

3236
public function isNotNullNonLiteralString(EntityManagerInterface $em): void
3337
{
34-
$field = strtolower('field'); // Non literal-string, e.g. $_POST['field'];
38+
$field = $this->nonLiteralString('field');
3539
$result = $em->createQueryBuilder()->expr()->isNotNull($field);
36-
assertType("'field IS NOT NULL'", $result);
40+
assertType('string', $result);
3741
}
3842

39-
public function countDistinctLiteralString(EntityManagerInterface $em): void
43+
public function betweenLiteralString(EntityManagerInterface $em): void
4044
{
41-
$result = $em->createQueryBuilder()->expr()->countDistinct('A', 'B', 'C');
42-
assertType("'COUNT(DISTINCT A, B, C)'", $result); // A ConstantStringType isLiteralString
45+
$result = $em->createQueryBuilder()->expr()->between('field', "'value_1'", "'value_2'");
46+
assertType("'field BETWEEN \'value_1\' AND \'value_2\''", $result); // A ConstantStringType isLiteralString
4347
}
4448

45-
public function countDistinctNonLiteralString(EntityManagerInterface $em): void
49+
public function betweenNonLiteralString1(EntityManagerInterface $em): void
4650
{
47-
$field = strtolower('B'); // Non literal-string, e.g. $_POST['field'];
48-
$result = $em->createQueryBuilder()->expr()->countDistinct('A', $field, 'C');
49-
assertType("'COUNT(DISTINCT A, b, C)'", $result);
51+
$value = $this->nonLiteralString('A');
52+
$result = $em->createQueryBuilder()->expr()->between($value, "'value_1'", "'value_2'");
53+
assertType('string', $result);
5054
}
5155

52-
public function betweenLiteralString(EntityManagerInterface $em): void
56+
public function betweenNonLiteralString2(EntityManagerInterface $em): void
5357
{
54-
$result = $em->createQueryBuilder()->expr()->between('field', "'value_1'", "'value_2'");
55-
assertType("'field BETWEEN \'value_1\' AND \'value_2\''", $result); // A ConstantStringType isLiteralString
58+
$value = $this->nonLiteralString('A');
59+
$result = $em->createQueryBuilder()->expr()->between('field', "'" . $value . "'", "'value_2'");
60+
assertType('string', $result);
5661
}
5762

58-
public function betweenNonLiteralString(EntityManagerInterface $em): void
63+
public function betweenNonLiteralString3(EntityManagerInterface $em): void
5964
{
60-
$value_1 = strtolower('B'); // Non literal-string, e.g. $_POST['field'];
61-
$result = $em->createQueryBuilder()->expr()->between('field', "'" . $value_1 . "'", "'value_2'");
62-
assertType("'field BETWEEN \'b\' AND \'value_2\''", $result);
65+
$value = $this->nonLiteralString('A');
66+
$result = $em->createQueryBuilder()->expr()->between('field', "'value_1'", "'" . $value . "'");
67+
assertType('string', $result);
6368
}
6469

6570
// Might be a problem, as these do not return a 'literal-string'.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace QueryResult\CreateQuery;
4+
5+
use Doctrine\ORM\AbstractQuery;
6+
use Doctrine\ORM\EntityManagerInterface;
7+
use Doctrine\ORM\QueryBuilder;
8+
use QueryResult\Entities\Many;
9+
use function PHPStan\Testing\assertType;
10+
11+
class ExpressionBuilderGetQueryNoObjectManager
12+
{
13+
private function nonLiteralString(string $value): string {
14+
return $value; // Using the 'string' return type to provide a non `literal-string`, e.g. $_POST['field'];
15+
}
16+
17+
public function isNullLiteralString(EntityManagerInterface $em): void
18+
{
19+
$result = $em->createQueryBuilder()->expr()->isNull('field');
20+
assertType('literal-string&non-empty-string', $result);
21+
}
22+
23+
public function isNullNonLiteralString(EntityManagerInterface $em): void
24+
{
25+
$field = $this->nonLiteralString('field');
26+
$result = $em->createQueryBuilder()->expr()->isNull($field);
27+
assertType('string', $result);
28+
}
29+
30+
public function isNotNullLiteralString(EntityManagerInterface $em): void
31+
{
32+
$result = $em->createQueryBuilder()->expr()->isNotNull('field');
33+
assertType('literal-string&non-empty-string', $result);
34+
}
35+
36+
public function isNotNullNonLiteralString(EntityManagerInterface $em): void
37+
{
38+
$field = $this->nonLiteralString('field');
39+
$result = $em->createQueryBuilder()->expr()->isNotNull($field);
40+
assertType('string', $result);
41+
}
42+
43+
public function betweenLiteralString(EntityManagerInterface $em): void
44+
{
45+
$result = $em->createQueryBuilder()->expr()->between('field', "'value_1'", "'value_2'");
46+
assertType('literal-string&non-empty-string', $result);
47+
}
48+
49+
public function betweenNonLiteralString1(EntityManagerInterface $em): void
50+
{
51+
$value = $this->nonLiteralString('A');
52+
$result = $em->createQueryBuilder()->expr()->between($value, "'value_1'", "'value_2'");
53+
assertType('string', $result);
54+
}
55+
56+
public function betweenNonLiteralString2(EntityManagerInterface $em): void
57+
{
58+
$value = $this->nonLiteralString('A');
59+
$result = $em->createQueryBuilder()->expr()->between('field', "'" . $value . "'", "'value_2'");
60+
assertType('string', $result);
61+
}
62+
63+
public function betweenNonLiteralString3(EntityManagerInterface $em): void
64+
{
65+
$value = $this->nonLiteralString('A');
66+
$result = $em->createQueryBuilder()->expr()->between('field', "'value_1'", "'" . $value . "'");
67+
assertType('string', $result);
68+
}
69+
70+
// Might be a problem, as these do not return a 'literal-string'.
71+
// As in, functions to support MOD() and ABS() return stringable value objects (Expr\Func).
72+
public function isNullNonLiteralStringExprFunc(EntityManagerInterface $em): void
73+
{
74+
$result = $em->createQueryBuilder()->expr()->isNull($qb->expr()->mod('field', '0'));
75+
assertType('string', $result);
76+
}
77+
78+
public function betweenNonLiteralStringExprFunc(EntityManagerInterface $em): void
79+
{
80+
$result = $em->createQueryBuilder()->expr()->between($qb->expr()->abs('field'), '10', '30');
81+
assertType('string', $result);
82+
}
83+
84+
}

0 commit comments

Comments
 (0)