Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 157f942

Browse files
committed
QueryResultTypeWalker: precise type inferring
1 parent ad91388 commit 157f942

22 files changed

+5448
-596
lines changed

‎README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ Queries are analyzed statically and do not require a running database server. Th
128128

129129
Most DQL features are supported, including `GROUP BY`, `DISTINCT`, all flavors of `JOIN`, arithmetic expressions, functions, aggregations, `NEW`, etc. Sub queries and `INDEX BY` are not yet supported (infered type will be `mixed`).
130130

131+
### Query type inference of expressions
132+
133+
Whether e.g. `SUM(e.column)` is fetched as `float`, `numeric-string` or `int` highly [depends on drivers, their setup and PHP version](https://github.com/janedbal/php-database-drivers-fetch-test).
134+
This extension autodetects your setup and provides quite accurate results for `pdo_mysql`, `mysqli`, `pdo_sqlite`, `sqlite3`, `pdo_pgsql` and `pgsql`.
135+
Sadly, this autodetection often needs real database connection, so in order to utilize precise types, your `objectManagerLoader` need to be able to connect to real database.
136+
137+
If you are using `bleedingEdge`, the connection failure is propagated. If not, it will be silently ignored and the type will be `mixed` or an union of possible types.
138+
131139
### Supported methods
132140

133141
The `getResult` method is supported when called without argument, or with the hydrateMode argument set to `Query::HYDRATE_OBJECT`:

‎phpstan.neon‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,25 @@ parameters:
4242
-
4343
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\ORM\\\\EntityManager'' and ''create'' will always evaluate to true\.$#'
4444
path: src/Doctrine/Mapping/ClassMetadataFactory.php
45-
reportUnmatched: false
46-
-
47-
messages:
48-
- '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Connection and ''getNativeConnection'' will always evaluate to true\.$#'
49-
- '#^Cannot call method getWrappedResourceHandle\(\) on class\-string\|object\.$#'
50-
path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php
51-
reportUnmatched: false
5245

5346
-
5447
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Connection and ''getNativeConnection'' will always evaluate to true\.$#' # needed for older DBAL versions
5548
paths:
49+
- src/Type/Doctrine/Query/QueryResultTypeWalker.php
5650
- src/Doctrine/Driver/DriverDetector.php
5751

5852
-
5953
messages: # needed for older DBAL versions
6054
- '#^Class PgSql\\Connection not found\.$#'
6155
- '#^Class Doctrine\\DBAL\\Driver\\PgSQL\\Driver not found\.$#'
6256
- '#^Class Doctrine\\DBAL\\Driver\\SQLite3\\Driver not found\.$#'
57+
58+
-
59+
message: '#^Call to an undefined method Doctrine\\DBAL\\Connection\:\:getWrappedConnection\(\)\.$#' # dropped in DBAL 4
60+
path: src/Type/Doctrine/Query/QueryResultTypeWalker.php
61+
62+
-
63+
messages: # oldest dbal has only getSchemaManager, dbal4 has only createSchemaManager
64+
- '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Connection and ''createSchemaManager'' will always evaluate to true\.$#'
65+
- '#^Call to an undefined method Doctrine\\DBAL\\Connection\:\:getSchemaManager\(\)\.$#'
66+
path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php

‎src/Doctrine/Driver/DriverDetector.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public function __construct(bool $failOnInvalidConnection)
4646
$this->failOnInvalidConnection = $failOnInvalidConnection;
4747
}
4848

49+
public function failsOnInvalidConnection(): bool
50+
{
51+
return $this->failOnInvalidConnection;
52+
}
53+
4954
/**
5055
* @return self::*|null
5156
*/

‎src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Doctrine\Persistence\Mapping\MappingException;
1313
use PhpParser\Node\Expr\MethodCall;
1414
use PHPStan\Analyser\Scope;
15+
use PHPStan\Doctrine\Driver\DriverDetector;
16+
use PHPStan\Php\PhpVersion;
1517
use PHPStan\Reflection\MethodReflection;
1618
use PHPStan\Type\Constant\ConstantStringType;
1719
use PHPStan\Type\Doctrine\Query\QueryResultTypeBuilder;
@@ -37,10 +39,23 @@ final class CreateQueryDynamicReturnTypeExtension implements DynamicMethodReturn
3739
/** @var DescriptorRegistry */
3840
private $descriptorRegistry;
3941

40-
public function __construct(ObjectMetadataResolver $objectMetadataResolver, DescriptorRegistry $descriptorRegistry)
42+
/** @var PhpVersion */
43+
private $phpVersion;
44+
45+
/** @var DriverDetector */
46+
private $driverDetector;
47+
48+
public function __construct(
49+
ObjectMetadataResolver $objectMetadataResolver,
50+
DescriptorRegistry $descriptorRegistry,
51+
PhpVersion $phpVersion,
52+
DriverDetector $driverDetector
53+
)
4154
{
4255
$this->objectMetadataResolver = $objectMetadataResolver;
4356
$this->descriptorRegistry = $descriptorRegistry;
57+
$this->phpVersion = $phpVersion;
58+
$this->driverDetector = $driverDetector;
4459
}
4560

4661
public function getClass(): string
@@ -87,7 +102,7 @@ public function getTypeFromMethodCall(
87102

88103
try {
89104
$query = $em->createQuery($queryString);
90-
QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry);
105+
QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry, $this->phpVersion, $this->driverDetector);
91106
} catch (ORMException | DBALException | NewDBALException | CommonException | MappingException | \Doctrine\ORM\Exception\ORMException $e) {
92107
return new QueryType($queryString, null, null);
93108
} catch (AssertionError $e) {

‎src/Type/Doctrine/Descriptors/FloatType.php‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ public function getWritableToDatabaseType(): Type
4040

4141
public function getDatabaseInternalType(): Type
4242
{
43-
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new IntegerType());
43+
return TypeCombinator::union(
44+
new \PHPStan\Type\FloatType(),
45+
new IntersectionType([
46+
new StringType(),
47+
new AccessoryNumericStringType(),
48+
])
49+
);
4450
}
4551

4652
public function getDatabaseInternalTypeForDriver(Connection $connection): Type

‎src/Type/Doctrine/Descriptors/ReflectionDescriptor.php‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Connection;
56
use Doctrine\DBAL\Platforms\AbstractPlatform;
67
use Doctrine\DBAL\Types\Type as DbalType;
78
use PHPStan\DependencyInjection\Container;
@@ -14,7 +15,7 @@
1415
use PHPStan\Type\Type;
1516
use PHPStan\Type\TypeCombinator;
1617

17-
class ReflectionDescriptor implements DoctrineTypeDescriptor
18+
class ReflectionDescriptor implements DoctrineTypeDescriptor, DoctrineTypeDriverAwareDescriptor
1819
{
1920

2021
/** @var class-string<DbalType> */
@@ -90,4 +91,27 @@ public function getDatabaseInternalType(): Type
9091
return new MixedType();
9192
}
9293

94+
public function getDatabaseInternalTypeForDriver(Connection $connection): Type
95+
{
96+
$registry = $this->container->getByType(DefaultDescriptorRegistry::class);
97+
$parents = $this->reflectionProvider->getClass($this->type)->getParentClassesNames();
98+
99+
foreach ($parents as $dbalTypeParentClass) {
100+
try {
101+
// this assumes that if somebody inherits from DecimalType,
102+
// the real database type remains decimal and we can reuse its descriptor
103+
$descriptor = $registry->getByClassName($dbalTypeParentClass);
104+
105+
return $descriptor instanceof DoctrineTypeDriverAwareDescriptor
106+
? $descriptor->getDatabaseInternalTypeForDriver($connection)
107+
: $descriptor->getDatabaseInternalType();
108+
109+
} catch (DescriptorNotRegisteredException $e) {
110+
continue;
111+
}
112+
}
113+
114+
return new MixedType();
115+
}
116+
93117
}
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\Query;
4+
5+
use Doctrine\ORM\Query\AST\Literal;
6+
use PHPStan\Type\Constant\ConstantStringType;
7+
8+
class DqlConstantStringType extends ConstantStringType
9+
{
10+
11+
/** @var Literal::* */
12+
private $originLiteralType;
13+
14+
/**
15+
* @param Literal::* $originLiteralType
16+
*/
17+
public function __construct(string $value, int $originLiteralType)
18+
{
19+
parent::__construct($value, false);
20+
$this->originLiteralType = $originLiteralType;
21+
}
22+
23+
/**
24+
* @return Literal::*
25+
*/
26+
public function getOriginLiteralType(): int
27+
{
28+
return $this->originLiteralType;
29+
}
30+
31+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /