Skip to content

Commit e444a78

Browse files
committed
Support for infering the result type of queries in EntityManager::createQuery()
Adds a type parameter "TResult" on the Doctrine\ORM\Query and Doctrine\ORM\AbstractQuery stubs. This parameter represents the type of the results returned by the getResult() and execute() family of methods, when in HYDRATE_OBJECT mode. Also adds a DynamicReturnTypeExtension on EntityManager::createQuery() so that TResult is infered from the query string. Caveat: As the hydration mode influences the return type of the getResult() and execute() family of methods, we can not just declare that these methods return TResult yet. This will require a DynamicReturnTypeExtension.
1 parent 1a40581 commit e444a78

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3817
-6
lines changed

Diff for: extension.neon

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ parameters:
3838
- stubs/ORM/AbstractQuery.stub
3939
- stubs/ORM/Mapping/ClassMetadata.stub
4040
- stubs/ORM/Mapping/ClassMetadataInfo.stub
41+
- stubs/ORM/Query.stub
4142
- stubs/Persistence/Mapping/ClassMetadata.stub
4243
- stubs/ServiceDocumentRepository.stub
4344

@@ -120,6 +121,12 @@ services:
120121
class: PHPStan\Type\Doctrine\Query\QueryGetDqlDynamicReturnTypeExtension
121122
tags:
122123
- phpstan.broker.dynamicMethodReturnTypeExtension
124+
-
125+
class: PHPStan\Type\Doctrine\CreateQueryDynamicReturnTypeExtension
126+
arguments:
127+
objectMetadataResolver: @PHPStan\Type\Doctrine\ObjectMetadataResolver
128+
tags:
129+
- phpstan.broker.dynamicMethodReturnTypeExtension
123130
-
124131
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\ExpressionBuilderDynamicReturnTypeExtension
125132
arguments:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine;
4+
5+
use Doctrine\Common\CommonException;
6+
use Doctrine\DBAL\DBALException;
7+
use Doctrine\ORM\EntityManagerInterface;
8+
use Doctrine\ORM\ORMException;
9+
use Doctrine\ORM\Query;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PHPStan\Analyser\Scope;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Type\Constant\ConstantStringType;
14+
use PHPStan\Type\Doctrine\Query\QueryResultTypeBuilder;
15+
use PHPStan\Type\Doctrine\Query\QueryResultTypeWalker;
16+
use PHPStan\Type\Doctrine\Query\QueryType;
17+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
18+
use PHPStan\Type\Generic\GenericObjectType;
19+
use PHPStan\Type\IntersectionType;
20+
use PHPStan\Type\MixedType;
21+
use PHPStan\Type\Type;
22+
use PHPStan\Type\TypeTraverser;
23+
use PHPStan\Type\UnionType;
24+
25+
/**
26+
* Infers TResult in Query<TResult> on EntityManagerInterface::createQuery()
27+
*/
28+
final class CreateQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
29+
{
30+
31+
/** @var ObjectMetadataResolver */
32+
private $objectMetadataResolver;
33+
34+
/** @var DescriptorRegistry */
35+
private $descriptorRegistry;
36+
37+
public function __construct(ObjectMetadataResolver $objectMetadataResolver, DescriptorRegistry $descriptorRegistry)
38+
{
39+
$this->objectMetadataResolver = $objectMetadataResolver;
40+
$this->descriptorRegistry = $descriptorRegistry;
41+
}
42+
43+
public function getClass(): string
44+
{
45+
return EntityManagerInterface::class;
46+
}
47+
48+
public function isMethodSupported(MethodReflection $methodReflection): bool
49+
{
50+
return $methodReflection->getName() === 'createQuery';
51+
}
52+
53+
public function getTypeFromMethodCall(
54+
MethodReflection $methodReflection,
55+
MethodCall $methodCall,
56+
Scope $scope
57+
): Type
58+
{
59+
$queryStringArgIndex = 0;
60+
$args = $methodCall->getArgs();
61+
62+
if (!isset($args[$queryStringArgIndex])) {
63+
return new GenericObjectType(
64+
Query::class,
65+
[new MixedType()]
66+
);
67+
}
68+
69+
$argType = $scope->getType($args[$queryStringArgIndex]->value);
70+
71+
return TypeTraverser::map($argType, function (Type $type, callable $traverse): Type {
72+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
73+
return $traverse($type);
74+
}
75+
if ($type instanceof ConstantStringType) {
76+
$queryString = $type->getValue();
77+
78+
$em = $this->objectMetadataResolver->getObjectManager();
79+
if (!$em instanceof EntityManagerInterface) {
80+
return new QueryType($queryString, null);
81+
}
82+
83+
$typeBuilder = new QueryResultTypeBuilder();
84+
85+
try {
86+
$query = $em->createQuery($queryString);
87+
QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry);
88+
} catch (ORMException | DBALException | CommonException $e) {
89+
return new QueryType($queryString, null);
90+
}
91+
92+
return new QueryType($queryString, $typeBuilder->getResultType());
93+
}
94+
return new GenericObjectType(
95+
Query::class,
96+
[new MixedType()]
97+
);
98+
});
99+
}
100+
101+
}

Diff for: src/Type/Doctrine/Descriptors/ArrayType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new \PHPStan\Type\StringType();
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/BigIntType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return TypeCombinator::union(new \PHPStan\Type\StringType(), new \PHPStan\Type\IntegerType());
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new \PHPStan\Type\IntegerType();
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/BinaryType.php

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Type\MixedType;
66
use PHPStan\Type\ResourceType;
7+
use PHPStan\Type\StringType;
78
use PHPStan\Type\Type;
89

910
class BinaryType implements DoctrineTypeDescriptor
@@ -24,4 +25,9 @@ public function getWritableToDatabaseType(): Type
2425
return new MixedType();
2526
}
2627

28+
public function getDatabaseInternalType(): Type
29+
{
30+
return new StringType();
31+
}
32+
2733
}

Diff for: src/Type/Doctrine/Descriptors/BlobType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new MixedType();
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new MixedType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/BooleanType.php

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type\Doctrine\Descriptors;
44

55
use PHPStan\Type\Type;
6+
use PHPStan\Type\TypeCombinator;
67

78
class BooleanType implements DoctrineTypeDescriptor
89
{
@@ -22,4 +23,12 @@ public function getWritableToDatabaseType(): Type
2223
return new \PHPStan\Type\BooleanType();
2324
}
2425

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return TypeCombinator::union(
29+
new \PHPStan\Type\Constant\ConstantIntegerType(0),
30+
new \PHPStan\Type\Constant\ConstantIntegerType(1)
31+
);
32+
}
33+
2534
}

Diff for: src/Type/Doctrine/Descriptors/DateImmutableType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new ObjectType(DateTimeImmutable::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DateIntervalType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new ObjectType(DateInterval::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DateTimeImmutableType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new ObjectType(DateTimeImmutable::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DateTimeType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new \PHPStan\Type\ObjectType(DateTimeInterface::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DateTimeTzImmutableType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new ObjectType(DateTimeImmutable::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DateTimeTzType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new \PHPStan\Type\ObjectType(DateTimeInterface::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DateType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return new \PHPStan\Type\ObjectType(DateTimeInterface::class);
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return new \PHPStan\Type\StringType();
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DecimalType.php

+5
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
2424
return TypeCombinator::union(new \PHPStan\Type\StringType(), new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
2525
}
2626

27+
public function getDatabaseInternalType(): Type
28+
{
29+
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
30+
}
31+
2732
}

Diff for: src/Type/Doctrine/Descriptors/DoctrineTypeDescriptor.php

+2
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ public function getWritableToPropertyType(): Type;
1616

1717
public function getWritableToDatabaseType(): Type;
1818

19+
public function getDatabaseInternalType(): Type;
20+
1921
}

Diff for: src/Type/Doctrine/Descriptors/FloatType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/GuidType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return new StringType();
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new StringType();
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/IntegerType.php

+5
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ public function getWritableToDatabaseType(): Type
2222
return new \PHPStan\Type\IntegerType();
2323
}
2424

25+
public function getDatabaseInternalType(): Type
26+
{
27+
return new \PHPStan\Type\IntegerType();
28+
}
29+
2530
}

Diff for: src/Type/Doctrine/Descriptors/JsonArrayType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new \PHPStan\Type\StringType();
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/JsonType.php

+5
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ public function getWritableToDatabaseType(): Type
4949
return self::getJsonType();
5050
}
5151

52+
public function getDatabaseInternalType(): Type
53+
{
54+
return new StringType();
55+
}
56+
5257
}

Diff for: src/Type/Doctrine/Descriptors/ObjectType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return new ObjectWithoutClassType();
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new \PHPStan\Type\StringType();
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php

+5
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ public function getWritableToDatabaseType(): Type
5454
);
5555
}
5656

57+
public function getDatabaseInternalType(): Type
58+
{
59+
return new \PHPStan\Type\StringType();
60+
}
61+
5762
}

Diff for: src/Type/Doctrine/Descriptors/ReflectionDescriptor.php

+5
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ public function getWritableToDatabaseType(): Type
4545
return TypeCombinator::removeNull($type);
4646
}
4747

48+
public function getDatabaseInternalType(): Type
49+
{
50+
return new \PHPStan\Type\MixedType();
51+
}
52+
4853
}

Diff for: src/Type/Doctrine/Descriptors/SimpleArrayType.php

+5
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
2323
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
2424
}
2525

26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new \PHPStan\Type\StringType();
29+
}
30+
2631
}

Diff for: src/Type/Doctrine/Descriptors/SmallIntType.php

+5
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ public function getWritableToDatabaseType(): Type
2222
return new \PHPStan\Type\IntegerType();
2323
}
2424

25+
public function getDatabaseInternalType(): Type
26+
{
27+
return new \PHPStan\Type\IntegerType();
28+
}
29+
2530
}

Diff for: src/Type/Doctrine/Descriptors/StringType.php

+5
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ public function getWritableToDatabaseType(): Type
2222
return new \PHPStan\Type\StringType();
2323
}
2424

25+
public function getDatabaseInternalType(): Type
26+
{
27+
return new \PHPStan\Type\StringType();
28+
}
29+
2530
}

Diff for: src/Type/Doctrine/Descriptors/TextType.php

+5
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,9 @@ public function getWritableToDatabaseType(): Type
2222
return new \PHPStan\Type\StringType();
2323
}
2424

25+
public function getDatabaseInternalType(): Type
26+
{
27+
return new \PHPStan\Type\StringType();
28+
}
29+
2530
}

0 commit comments

Comments
 (0)