Skip to content

Commit d2af4bf

Browse files
committed
QueryResultTypeWalker: precise type inferring
1 parent dd71401 commit d2af4bf

Some content is hidden

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

42 files changed

+2402
-428
lines changed

Diff for: README.md

+6
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ $query->getOneOrNullResult(Query::HYDRATE_OBJECT); // User
152152

153153
This is due to the design of the `Query` class preventing from determining the hydration mode used by these functions unless it is specified explicitly during the call.
154154

155+
### Expression types inferring
156+
157+
Whether `MAX(e.id)` is fetched as `string` or `int` highly [depends on drivers, their setup and PHP version](https://github.com/janedbal/php-database-drivers-fetch-test).
158+
This extension copies the logic from linked analysis, autodetects your setup and provides accurate results for `pdo_mysql`, `mysqli`, `pdo_sqlite`, `sqlite3`, `pdo_pgsql` and `pgsql`.
159+
Any other driver will result in union with stringified version, e.g. `numeric-string|int`.
160+
155161
### Problematic approaches
156162

157163
Not every QueryBuilder can be statically analysed, here are few advices to maximize type inferring:

Diff for: phpstan.neon

+8
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,11 @@ parameters:
4949
- '#^Cannot call method getWrappedResourceHandle\(\) on class\-string\|object\.$#'
5050
path: tests/Platform/QueryResultTypeWalkerFetchTypeMatrixTest.php
5151
reportUnmatched: false
52+
-
53+
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Connection and ''getNativeConnection'' will always evaluate to true\.$#' # needed for older DBAL versions
54+
path: src/Type/Doctrine/Query/QueryResultTypeWalker.php
55+
-
56+
messages: # needed for older DBAL versions (fails only on PHP 7.3)
57+
- '#^Class Doctrine\\DBAL\\Driver\\PgSQL\\Driver not found\.$#'
58+
- '#^Class Doctrine\\DBAL\\Driver\\SQLite3\\Driver not found\.$#'
59+
path: src/Type/Doctrine/Query/QueryResultTypeWalker.php

Diff for: src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Doctrine\Persistence\Mapping\MappingException;
1313
use PhpParser\Node\Expr\MethodCall;
1414
use PHPStan\Analyser\Scope;
15+
use PHPStan\Php\PhpVersion;
1516
use PHPStan\Reflection\MethodReflection;
1617
use PHPStan\Type\Constant\ConstantStringType;
1718
use PHPStan\Type\Doctrine\Query\QueryResultTypeBuilder;
@@ -37,10 +38,14 @@ final class CreateQueryDynamicReturnTypeExtension implements DynamicMethodReturn
3738
/** @var DescriptorRegistry */
3839
private $descriptorRegistry;
3940

40-
public function __construct(ObjectMetadataResolver $objectMetadataResolver, DescriptorRegistry $descriptorRegistry)
41+
/** @var PhpVersion */
42+
private $phpVersion;
43+
44+
public function __construct(ObjectMetadataResolver $objectMetadataResolver, DescriptorRegistry $descriptorRegistry, PhpVersion $phpVersion)
4145
{
4246
$this->objectMetadataResolver = $objectMetadataResolver;
4347
$this->descriptorRegistry = $descriptorRegistry;
48+
$this->phpVersion = $phpVersion;
4449
}
4550

4651
public function getClass(): string
@@ -87,7 +92,7 @@ public function getTypeFromMethodCall(
8792

8893
try {
8994
$query = $em->createQuery($queryString);
90-
QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry);
95+
QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry, $this->phpVersion);
9196
} catch (ORMException | DBALException | NewDBALException | CommonException | MappingException | \Doctrine\ORM\Exception\ORMException $e) {
9297
return new QueryType($queryString, null, null);
9398
} catch (AssertionError $e) {

Diff for: src/Type/Doctrine/DefaultDescriptorRegistry.php

+11
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,15 @@ public function get(string $type): DoctrineTypeDescriptor
3636
return $this->descriptors[$typeClass];
3737
}
3838

39+
/**
40+
* @throws DescriptorNotRegisteredException
41+
*/
42+
public function getByClassName(string $className): DoctrineTypeDescriptor
43+
{
44+
if (!isset($this->descriptors[$className])) {
45+
throw new DescriptorNotRegisteredException();
46+
}
47+
return $this->descriptors[$className];
48+
}
49+
3950
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
56
use PHPStan\Type\MixedType;
67
use PHPStan\Type\StringType;
78
use PHPStan\Type\Type;
@@ -24,7 +25,7 @@ public function getWritableToDatabaseType(): Type
2425
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
2526
}
2627

27-
public function getDatabaseInternalType(): Type
28+
public function getDatabaseInternalType(Driver $driver): Type
2829
{
2930
return new StringType();
3031
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
56
use PHPStan\Type\StringType;
67
use PHPStan\Type\Type;
78

@@ -23,7 +24,7 @@ public function getWritableToDatabaseType(): Type
2324
return new StringType();
2425
}
2526

26-
public function getDatabaseInternalType(): Type
27+
public function getDatabaseInternalType(Driver $driver): Type
2728
{
2829
return new StringType();
2930
}

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

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

55
use Composer\InstalledVersions;
6+
use Doctrine\DBAL\Driver;
67
use PHPStan\Type\Accessory\AccessoryNumericStringType;
78
use PHPStan\Type\IntegerType;
89
use PHPStan\Type\StringType;
@@ -30,10 +31,10 @@ public function getWritableToPropertyType(): Type
3031

3132
public function getWritableToDatabaseType(): Type
3233
{
33-
return TypeCombinator::union(new StringType(), new IntegerType());
34+
return TypeCombinator::union(new StringType());
3435
}
3536

36-
public function getDatabaseInternalType(): Type
37+
public function getDatabaseInternalType(Driver $driver): Type
3738
{
3839
return new IntegerType();
3940
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
56
use PHPStan\Type\MixedType;
67
use PHPStan\Type\ResourceType;
78
use PHPStan\Type\StringType;
@@ -25,7 +26,7 @@ public function getWritableToDatabaseType(): Type
2526
return new MixedType();
2627
}
2728

28-
public function getDatabaseInternalType(): Type
29+
public function getDatabaseInternalType(Driver $driver): Type
2930
{
3031
return new StringType();
3132
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
56
use PHPStan\Type\MixedType;
67
use PHPStan\Type\ResourceType;
78
use PHPStan\Type\Type;
@@ -24,7 +25,7 @@ public function getWritableToDatabaseType(): Type
2425
return new MixedType();
2526
}
2627

27-
public function getDatabaseInternalType(): Type
28+
public function getDatabaseInternalType(Driver $driver): Type
2829
{
2930
return new MixedType();
3031
}

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
6+
use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as PdoPgSQLDriver;
7+
use Doctrine\DBAL\Driver\PgSQL\Driver as PgSQLDriver;
58
use PHPStan\Type\Constant\ConstantIntegerType;
69
use PHPStan\Type\Type;
710
use PHPStan\Type\TypeCombinator;
@@ -24,12 +27,15 @@ public function getWritableToDatabaseType(): Type
2427
return new \PHPStan\Type\BooleanType();
2528
}
2629

27-
public function getDatabaseInternalType(): Type
30+
public function getDatabaseInternalType(Driver $driver): Type
2831
{
32+
if ($driver instanceof PgSQLDriver || $driver instanceof PdoPgSQLDriver) {
33+
return new \PHPStan\Type\BooleanType();
34+
}
35+
2936
return TypeCombinator::union(
3037
new ConstantIntegerType(0),
31-
new ConstantIntegerType(1),
32-
new \PHPStan\Type\BooleanType()
38+
new ConstantIntegerType(1)
3339
);
3440
}
3541

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

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

55
use DateTimeImmutable;
6+
use Doctrine\DBAL\Driver;
67
use PHPStan\Type\ObjectType;
78
use PHPStan\Type\StringType;
89
use PHPStan\Type\Type;
@@ -25,7 +26,7 @@ public function getWritableToDatabaseType(): Type
2526
return new ObjectType(DateTimeImmutable::class);
2627
}
2728

28-
public function getDatabaseInternalType(): Type
29+
public function getDatabaseInternalType(Driver $driver): Type
2930
{
3031
return new StringType();
3132
}

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

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

55
use DateInterval;
6+
use Doctrine\DBAL\Driver;
67
use PHPStan\Type\ObjectType;
78
use PHPStan\Type\StringType;
89
use PHPStan\Type\Type;
@@ -25,7 +26,7 @@ public function getWritableToDatabaseType(): Type
2526
return new ObjectType(DateInterval::class);
2627
}
2728

28-
public function getDatabaseInternalType(): Type
29+
public function getDatabaseInternalType(Driver $driver): Type
2930
{
3031
return new StringType();
3132
}

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

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

55
use DateTimeImmutable;
6+
use Doctrine\DBAL\Driver;
67
use PHPStan\Type\ObjectType;
78
use PHPStan\Type\StringType;
89
use PHPStan\Type\Type;
@@ -25,7 +26,7 @@ public function getWritableToDatabaseType(): Type
2526
return new ObjectType(DateTimeImmutable::class);
2627
}
2728

28-
public function getDatabaseInternalType(): Type
29+
public function getDatabaseInternalType(Driver $driver): Type
2930
{
3031
return new StringType();
3132
}

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

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

55
use DateTime;
66
use DateTimeInterface;
7+
use Doctrine\DBAL\Driver;
78
use PHPStan\Type\ObjectType;
89
use PHPStan\Type\StringType;
910
use PHPStan\Type\Type;
@@ -26,7 +27,7 @@ public function getWritableToDatabaseType(): Type
2627
return new ObjectType(DateTimeInterface::class);
2728
}
2829

29-
public function getDatabaseInternalType(): Type
30+
public function getDatabaseInternalType(Driver $driver): Type
3031
{
3132
return new StringType();
3233
}

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

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

55
use DateTimeImmutable;
6+
use Doctrine\DBAL\Driver;
67
use PHPStan\Type\ObjectType;
78
use PHPStan\Type\StringType;
89
use PHPStan\Type\Type;
@@ -25,7 +26,7 @@ public function getWritableToDatabaseType(): Type
2526
return new ObjectType(DateTimeImmutable::class);
2627
}
2728

28-
public function getDatabaseInternalType(): Type
29+
public function getDatabaseInternalType(Driver $driver): Type
2930
{
3031
return new StringType();
3132
}

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

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

55
use DateTime;
66
use DateTimeInterface;
7+
use Doctrine\DBAL\Driver;
78
use PHPStan\Type\ObjectType;
89
use PHPStan\Type\StringType;
910
use PHPStan\Type\Type;
@@ -26,7 +27,7 @@ public function getWritableToDatabaseType(): Type
2627
return new ObjectType(DateTimeInterface::class);
2728
}
2829

29-
public function getDatabaseInternalType(): Type
30+
public function getDatabaseInternalType(Driver $driver): Type
3031
{
3132
return new StringType();
3233
}

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

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

55
use DateTime;
66
use DateTimeInterface;
7+
use Doctrine\DBAL\Driver;
78
use PHPStan\Type\ObjectType;
89
use PHPStan\Type\StringType;
910
use PHPStan\Type\Type;
@@ -26,7 +27,7 @@ public function getWritableToDatabaseType(): Type
2627
return new ObjectType(DateTimeInterface::class);
2728
}
2829

29-
public function getDatabaseInternalType(): Type
30+
public function getDatabaseInternalType(Driver $driver): Type
3031
{
3132
return new StringType();
3233
}

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

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
6+
use Doctrine\DBAL\Driver\PDO\SQLite\Driver as PdoSqliteDriver;
7+
use Doctrine\DBAL\Driver\SQLite3\Driver as Sqlite3Driver;
58
use PHPStan\Type\Accessory\AccessoryNumericStringType;
69
use PHPStan\Type\FloatType;
710
use PHPStan\Type\IntegerType;
11+
use PHPStan\Type\IntersectionType;
812
use PHPStan\Type\StringType;
913
use PHPStan\Type\Type;
1014
use PHPStan\Type\TypeCombinator;
@@ -27,9 +31,17 @@ public function getWritableToDatabaseType(): Type
2731
return TypeCombinator::union(new StringType(), new FloatType(), new IntegerType());
2832
}
2933

30-
public function getDatabaseInternalType(): Type
34+
public function getDatabaseInternalType(Driver $driver): Type
3135
{
32-
return TypeCombinator::union(new FloatType(), new IntegerType());
36+
if ($driver instanceof Sqlite3Driver || $driver instanceof PdoSqliteDriver) {
37+
return new FloatType();
38+
}
39+
40+
// TODO use mixed as fallback for any untested driver or some guess?
41+
return new IntersectionType([
42+
new StringType(),
43+
new AccessoryNumericStringType(),
44+
]);
3345
}
3446

3547
}

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Doctrine\Descriptors;
44

5+
use Doctrine\DBAL\Driver;
56
use PHPStan\Type\Type;
67

78
/** @api */
@@ -13,10 +14,29 @@ interface DoctrineTypeDescriptor
1314
*/
1415
public function getType(): string;
1516

17+
/**
18+
* This is used for inferring direct column results, e.g. SELECT e.field
19+
* It should comply with convertToPHPValue return value
20+
*/
1621
public function getWritableToPropertyType(): Type;
1722

1823
public function getWritableToDatabaseType(): Type;
1924

20-
public function getDatabaseInternalType(): Type;
25+
/**
26+
* This is used for inferring how database fetches column of such type
27+
* It should return the native type without stringification that may occur on certain PHP versions or driver configuration
28+
*
29+
* This is not used for direct column type inferring,
30+
* but when such column appears in expression like SELECT MAX(e.field)
31+
*
32+
* See: https://github.com/janedbal/php-database-drivers-fetch-test
33+
*
34+
* mysql sqlite pdo_pgsql pgsql
35+
* - decimal: string float string string
36+
* - float: float float string float
37+
* - bigint: int int int int
38+
* - bool: int int bool bool
39+
*/
40+
public function getDatabaseInternalType(Driver $driver): Type;
2141

2242
}

0 commit comments

Comments
 (0)