Skip to content

Commit 3f3a066

Browse files
committed
Fix test for <= PHP8.1 and lowest deps
1 parent 661c370 commit 3f3a066

File tree

4 files changed

+402
-44
lines changed

4 files changed

+402
-44
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,11 @@ $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-
### Numeric types inferring
155+
### Expression types inferring
156156

157-
By default, any expression like `MAX(e.id)` results in `int|numeric-string` as certain drivers & PHP versions do cast the result to string.
158-
If you are using setup that does not do that, you can disable stringification of such expressions by setting `parameters.doctrine.stringifyExpressions` to `false`.
159-
160-
This should be accurate behaviour for [PHP 8.1.25+ with PDO driver](https://github.com/php/php-src/blob/php-8.1.25/UPGRADING#L122-L139) and disabled `PDO::ATTR_STRINGIFY_FETCHES` (which is default).
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`.
161160

162161
```neon
163162
parameters:

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,6 @@ parameters:
3737
-
3838
message: '#^Call to method getProperty\(\) on an unknown class PHPStan\\BetterReflection\\Reflection\\Adapter\\ReflectionEnum\.$#'
3939
path: src/Rules/Gedmo/PropertiesExtension.php
40+
-
41+
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Connection and ''getNativeConnection'' will always evaluate to true\.$#' # needed for older DBAL versions
42+
path: src/Type/Doctrine/Query/QueryResultTypeWalker.php

src/Type/Doctrine/Query/QueryResultTypeWalker.php

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
use function is_numeric;
5757
use function is_object;
5858
use function is_string;
59+
use function method_exists;
5960
use function serialize;
6061
use function sprintf;
6162
use function stripos;
@@ -843,28 +844,8 @@ public function walkSelectExpression($selectExpression)
843844
$resultAlias = $selectExpression->fieldIdentificationVariable ?? $this->scalarResultCounter++;
844845
$type = $this->unmarshalType($expr->dispatch($this));
845846

846-
if (class_exists(TypedExpression::class) && $expr instanceof TypedExpression) {
847-
$enforcedType = $this->resolveDoctrineType($expr->getReturnType()->getName());
848-
$type = TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($enforcedType): Type {
849-
if ($type instanceof UnionType || $type instanceof IntersectionType) {
850-
return $traverse($type);
851-
}
852-
if ($type instanceof NullType) {
853-
return $type;
854-
}
855-
if ($enforcedType->accepts($type, true)->yes()) {
856-
return $type;
857-
}
858-
if ($enforcedType instanceof StringType) {
859-
if ($type instanceof IntegerType || $type instanceof FloatType) {
860-
return TypeCombinator::union($type->toString(), $type);
861-
}
862-
if ($type instanceof BooleanType) {
863-
return TypeCombinator::union($type->toInteger()->toString(), $type);
864-
}
865-
}
866-
return $enforcedType;
867-
});
847+
if ($expr instanceof TypedExpression) {
848+
$type = $this->resolveDoctrineType($expr->getReturnType()->getName(), null, TypeCombinator::containsNull($type)); // TODO test nullability
868849
} else {
869850
// Expressions default to Doctrine's StringType, whose
870851
// convertToPHPValue() is a no-op. So the actual type depends on
@@ -1501,7 +1482,7 @@ private function hasAggregateFunction(AST\SelectStatement $AST): bool
15011482
private function shouldStringifyExpressions(Type $type): TrinaryLogic
15021483
{
15031484
$driver = $this->em->getConnection()->getDriver();
1504-
$nativeConnection = $this->em->getConnection()->getNativeConnection();
1485+
$nativeConnection = $this->getNativeConnection();
15051486

15061487
if ($nativeConnection instanceof PDO) {
15071488
$stringifyFetches = $this->isPdoStringifyEnabled($nativeConnection);
@@ -1580,7 +1561,16 @@ private function isPdoStringifyEnabled(PDO $pdo): bool
15801561
try {
15811562
return (bool) $pdo->getAttribute(PDO::ATTR_STRINGIFY_FETCHES);
15821563
} catch (PDOException $e) {
1583-
return false; // default
1564+
$selectOne = $pdo->query('SELECT 1');
1565+
if ($selectOne === false) {
1566+
return false; // this should not happen, just return attribute default value
1567+
}
1568+
$one = $selectOne->fetchColumn();
1569+
1570+
// string can be returned due to old PHP used or because ATTR_STRINGIFY_FETCHES is enabled,
1571+
// but it should not matter as it behaves the same way
1572+
// (the attribute is there to maintain BC)
1573+
return is_string($one);
15841574
}
15851575
}
15861576

@@ -1589,4 +1579,22 @@ private function isPdoEmulatePreparesEnabled(PDO $pdo): bool
15891579
return (bool) $pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES);
15901580
}
15911581

1582+
/**
1583+
* @return object|resource|null
1584+
*/
1585+
private function getNativeConnection()
1586+
{
1587+
$connection = $this->em->getConnection();
1588+
1589+
if (method_exists($connection, 'getNativeConnection')) {
1590+
return $connection->getNativeConnection();
1591+
}
1592+
1593+
if ($connection->getWrappedConnection() instanceof PDO) {
1594+
return $connection->getWrappedConnection();
1595+
}
1596+
1597+
return null;
1598+
}
1599+
15921600
}

0 commit comments

Comments
 (0)