Skip to content

Commit

Permalink
Merge branch '4.2.x' into 5.0.x
Browse files Browse the repository at this point in the history
* 4.2.x:
  Implement an EnumType for MySQL/MariaDB (doctrine#6536)
  Leverage the new PDO subclasses (doctrine#6532)
  PHPStan 1.12.6 (doctrine#6535)
  • Loading branch information
derrabus committed Oct 10, 2024
2 parents f041d52 + 9744af4 commit ef19927
Show file tree
Hide file tree
Showing 23 changed files with 410 additions and 19 deletions.
8 changes: 8 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ all drivers and middleware.

# Upgrade to 4.2

## Support for new PDO subclasses on PHP 8.4

On PHP 8.4, if you call `getNativeConnection()` on a connection established through one of the PDO drivers,
you will get an instance of the new PDO subclasses, e.g. `Pdo\Mysql` or `Pdo\Ppgsql` instead of just `PDO`.

However, this currently does not apply to persistent connections.
See https://github.com/php/php-src/issues/16314 for details.

## Minor BC break: incompatible query cache format

The query cache format has been changed to address the issue where a cached result with no rows would miss the metadata.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.2",
"phpstan/phpstan": "1.12.0",
"phpstan/phpstan": "1.12.6",
"phpstan/phpstan-phpunit": "1.4.0",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "10.5.30",
Expand Down
10 changes: 10 additions & 0 deletions docs/en/reference/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ type natively, this type is mapped to the ``string`` type internally.
Values retrieved from the database are always converted to PHP's ``string`` type
or ``null`` if no data is present.

enum
++++

Maps and converts a string which is one of a set of predefined values. This
type is specifically designed for MySQL and MariaDB, where it is mapped to
the native ``ENUM`` type. For other database vendors, this type is mapped to
a string field (``VARCHAR``) with the maximum length being the length of the
longest value in the set. Values retrieved from the database are always
converted to PHP's ``string`` type or ``null`` if no data is present.

Binary string types
^^^^^^^^^^^^^^^^^^^

Expand Down
4 changes: 4 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ parameters:
-
message: '#^Parameter \#2 \$callback of function array_reduce expects callable\(\(callable&TIn\)\|Closure\(mixed \$value\)\: mixed\|null, callable\(T\)\: T\)\: \(\(callable&TIn\)\|Closure\(mixed \$value\)\: mixed\|null\), Closure\(callable\|null, callable\)\: \(callable\(T\)\: T\) given\.$#'
path: src/Portability/Converter.php

# PHPStan does not know the new PDO classes yet.
- '~^Class Pdo\\\w+ not found\.$~'
- '~^Call to an undefined static method PDO\:\:connect\(\)\.$~'
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
Expand Down
14 changes: 14 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@
<file name="src/Driver/PgSQL/Statement.php"/>
</errorLevel>
</TypeDoesNotContainType>
<UndefinedClass>
<errorLevel type="suppress">
<!-- New PDO classes introduced in PHP 8.4 -->
<referencedClass name="Pdo\Mysql"/>
<referencedClass name="Pdo\Pgsql"/>
<referencedClass name="Pdo\Sqlite"/>
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
<errorLevel type="suppress">
<!-- See https://github.com/vimeo/psalm/issues/5472 -->
Expand All @@ -292,6 +300,12 @@
<referencedClass name="OCILob"/>
</errorLevel>
</UndefinedDocblockClass>
<UndefinedMethod>
<errorLevel type="suppress">
<!-- New PDO static constructor introduced in PHP 8.4 -->
<referencedMethod name="PDO::connect"/>
</errorLevel>
</UndefinedMethod>
<UnsupportedPropertyReferenceUsage>
<errorLevel type="suppress">
<!-- This code is valid -->
Expand Down
5 changes: 4 additions & 1 deletion src/Driver/PDO/MySQL/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration;
use Doctrine\DBAL\Driver\PDO\PDOConnect;
use PDO;
use PDOException;
use SensitiveParameter;
Expand All @@ -16,6 +17,8 @@

final class Driver extends AbstractMySQLDriver
{
use PDOConnect;

/**
* {@inheritDoc}
*/
Expand All @@ -39,7 +42,7 @@ public function connect(
unset($safeParams['password']);

try {
$pdo = new PDO(
$pdo = $this->doConnect(
$this->constructPdoDsn($safeParams),
$params['user'] ?? '',
$params['password'] ?? '',
Expand Down
5 changes: 4 additions & 1 deletion src/Driver/PDO/OCI/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration;
use Doctrine\DBAL\Driver\PDO\PDOConnect;
use PDO;
use PDOException;
use SensitiveParameter;
Expand All @@ -16,6 +17,8 @@

final class Driver extends AbstractOracleDriver
{
use PDOConnect;

/**
* {@inheritDoc}
*/
Expand All @@ -39,7 +42,7 @@ public function connect(
unset($safeParams['password']);

try {
$pdo = new PDO(
$pdo = $this->doConnect(
$this->constructPdoDsn($params),
$params['user'] ?? '',
$params['password'] ?? '',
Expand Down
28 changes: 28 additions & 0 deletions src/Driver/PDO/PDOConnect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Driver\PDO;

use PDO;

use const PHP_VERSION_ID;

/** @internal */
trait PDOConnect
{
/** @param array<int, mixed> $options */
private function doConnect(
string $dsn,
string $username,
string $password,
array $options,
): PDO {
// see https://github.com/php/php-src/issues/16314
if (PHP_VERSION_ID < 80400 || ($options[PDO::ATTR_PERSISTENT] ?? false) === true) {
return new PDO($dsn, $username, $password, $options);
}

return PDO::connect($dsn, $username, $password, $options);
}
}
5 changes: 4 additions & 1 deletion src/Driver/PDO/PgSQL/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration;
use Doctrine\DBAL\Driver\PDO\PDOConnect;
use PDO;
use PDOException;
use SensitiveParameter;
Expand All @@ -16,6 +17,8 @@

final class Driver extends AbstractPostgreSQLDriver
{
use PDOConnect;

/**
* {@inheritDoc}
*/
Expand All @@ -39,7 +42,7 @@ public function connect(
unset($safeParams['password']);

try {
$pdo = new PDO(
$pdo = $this->doConnect(
$this->constructPdoDsn($safeParams),
$params['user'] ?? '',
$params['password'] ?? '',
Expand Down
5 changes: 4 additions & 1 deletion src/Driver/PDO/SQLSrv/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection;
use Doctrine\DBAL\Driver\PDO\Exception as PDOException;
use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration;
use Doctrine\DBAL\Driver\PDO\PDOConnect;
use PDO;
use SensitiveParameter;

Expand All @@ -19,6 +20,8 @@

final class Driver extends AbstractSQLServerDriver
{
use PDOConnect;

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -52,7 +55,7 @@ public function connect(
unset($safeParams['password']);

try {
$pdo = new PDO(
$pdo = $this->doConnect(
$this->constructDsn($safeParams, $dsnOptions),
$params['user'] ?? '',
$params['password'] ?? '',
Expand Down
6 changes: 4 additions & 2 deletions src/Driver/PDO/SQLite/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Doctrine\DBAL\Driver\PDO\Connection;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\DBAL\Driver\PDO\Exception\InvalidConfiguration;
use PDO;
use Doctrine\DBAL\Driver\PDO\PDOConnect;
use PDOException;
use SensitiveParameter;

Expand All @@ -17,6 +17,8 @@

final class Driver extends AbstractSQLiteDriver
{
use PDOConnect;

/**
* {@inheritDoc}
*/
Expand All @@ -31,7 +33,7 @@ public function connect(
}

try {
$pdo = new PDO(
$pdo = $this->doConnect(
$this->constructPdoDsn(array_intersect_key($params, ['path' => true, 'memory' => true])),
$params['user'] ?? '',
$params['password'] ?? '',
Expand Down
30 changes: 30 additions & 0 deletions src/Exception/InvalidColumnType/ColumnValuesRequired.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Exception\InvalidColumnType;

use Doctrine\DBAL\Exception\InvalidColumnType;
use Doctrine\DBAL\Platforms\AbstractPlatform;

use function get_debug_type;
use function sprintf;

/** @psalm-immutable */
final class ColumnValuesRequired extends InvalidColumnType
{
/**
* @param AbstractPlatform $platform The target platform
* @param string $type The SQL column type
*/
public static function new(AbstractPlatform $platform, string $type): self
{
return new self(
sprintf(
'%s requires the values of a %s column to be specified',
get_debug_type($platform),
$type,
),
);
}
}
19 changes: 19 additions & 0 deletions src/Platforms/AbstractMySQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\InvalidColumnType\ColumnValuesRequired;
use Doctrine\DBAL\Platforms\Keywords\KeywordList;
use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords;
use Doctrine\DBAL\Schema\AbstractAsset;
Expand All @@ -16,12 +17,14 @@
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types\Types;

use function array_map;
use function array_merge;
use function array_unique;
use function array_values;
use function count;
use function implode;
use function in_array;
use function is_array;
use function is_numeric;
use function sprintf;
use function str_replace;
Expand Down Expand Up @@ -638,6 +641,21 @@ public function getDecimalTypeDeclarationSQL(array $column): string
return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column);
}

/**
* {@inheritDoc}
*/
public function getEnumDeclarationSQL(array $column): string
{
if (! isset($column['values']) || ! is_array($column['values']) || $column['values'] === []) {
throw ColumnValuesRequired::new($this, 'ENUM');
}

return sprintf('ENUM(%s)', implode(', ', array_map(
$this->quoteStringLiteral(...),
$column['values'],
)));
}

/**
* Get unsigned declaration for a column.
*
Expand Down Expand Up @@ -711,6 +729,7 @@ protected function initializeDoctrineTypeMappings(): void
'datetime' => Types::DATETIME_MUTABLE,
'decimal' => Types::DECIMAL,
'double' => Types::FLOAT,
'enum' => Types::ENUM,
'float' => Types::SMALLFLOAT,
'int' => Types::INTEGER,
'integer' => Types::INTEGER,
Expand Down
22 changes: 22 additions & 0 deletions src/Platforms/AbstractPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
use Doctrine\DBAL\Exception\InvalidColumnType\ColumnPrecisionRequired;
use Doctrine\DBAL\Exception\InvalidColumnType\ColumnScaleRequired;
use Doctrine\DBAL\Exception\InvalidColumnType\ColumnValuesRequired;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Platforms\Exception\NoColumnsSpecifiedForTable;
use Doctrine\DBAL\Platforms\Exception\NotSupported;
Expand Down Expand Up @@ -51,6 +52,8 @@
use function is_float;
use function is_int;
use function is_string;
use function max;
use function mb_strlen;
use function preg_quote;
use function preg_replace;
use function sprintf;
Expand Down Expand Up @@ -190,6 +193,25 @@ public function getBinaryTypeDeclarationSQL(array $column): string
}
}

/**
* Returns the SQL snippet to declare an ENUM column.
*
* Enum is a non-standard type that is especially popular in MySQL and MariaDB. By default, this method map to
* a simple VARCHAR field which allows us to deploy it on any platform, e.g. SQLite.
*
* @param array<string, mixed> $column
*
* @throws ColumnValuesRequired If the column definition does not contain any values.
*/
public function getEnumDeclarationSQL(array $column): string
{
if (! isset($column['values']) || ! is_array($column['values']) || $column['values'] === []) {
throw ColumnValuesRequired::new($this, 'ENUM');
}

return $this->getStringTypeDeclarationSQL(['length' => max(...array_map(mb_strlen(...), $column['values']))]);
}

/**
* Returns the SQL snippet to declare a GUID/UUID column.
*
Expand Down
Loading

0 comments on commit ef19927

Please sign in to comment.