diff --git a/UPGRADE.md b/UPGRADE.md
index fc47a4dfbec..4a4d3a33720 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -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.
diff --git a/composer.json b/composer.json
index 596ec02eec8..3ba2e6f56dc 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
diff --git a/docs/en/reference/types.rst b/docs/en/reference/types.rst
index 21dd2669911..8ebc7edff1b 100644
--- a/docs/en/reference/types.rst
+++ b/docs/en/reference/types.rst
@@ -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
^^^^^^^^^^^^^^^^^^^
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 5be21dcfa78..091cb490490 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -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
diff --git a/psalm.xml.dist b/psalm.xml.dist
index 63ed50abcb5..5898fc283af 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -281,6 +281,14 @@
+
+
+
+
+
+
+
+
@@ -292,6 +300,12 @@
+
+
+
+
+
+
diff --git a/src/Driver/PDO/MySQL/Driver.php b/src/Driver/PDO/MySQL/Driver.php
index fad4b55954c..919f8475583 100644
--- a/src/Driver/PDO/MySQL/Driver.php
+++ b/src/Driver/PDO/MySQL/Driver.php
@@ -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;
@@ -16,6 +17,8 @@
final class Driver extends AbstractMySQLDriver
{
+ use PDOConnect;
+
/**
* {@inheritDoc}
*/
@@ -39,7 +42,7 @@ public function connect(
unset($safeParams['password']);
try {
- $pdo = new PDO(
+ $pdo = $this->doConnect(
$this->constructPdoDsn($safeParams),
$params['user'] ?? '',
$params['password'] ?? '',
diff --git a/src/Driver/PDO/OCI/Driver.php b/src/Driver/PDO/OCI/Driver.php
index 45f3ea25b01..49882b0d6cb 100644
--- a/src/Driver/PDO/OCI/Driver.php
+++ b/src/Driver/PDO/OCI/Driver.php
@@ -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;
@@ -16,6 +17,8 @@
final class Driver extends AbstractOracleDriver
{
+ use PDOConnect;
+
/**
* {@inheritDoc}
*/
@@ -39,7 +42,7 @@ public function connect(
unset($safeParams['password']);
try {
- $pdo = new PDO(
+ $pdo = $this->doConnect(
$this->constructPdoDsn($params),
$params['user'] ?? '',
$params['password'] ?? '',
diff --git a/src/Driver/PDO/PDOConnect.php b/src/Driver/PDO/PDOConnect.php
new file mode 100644
index 00000000000..f14a97c07aa
--- /dev/null
+++ b/src/Driver/PDO/PDOConnect.php
@@ -0,0 +1,28 @@
+ $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);
+ }
+}
diff --git a/src/Driver/PDO/PgSQL/Driver.php b/src/Driver/PDO/PgSQL/Driver.php
index 39e8a9452f1..cdf411aff54 100644
--- a/src/Driver/PDO/PgSQL/Driver.php
+++ b/src/Driver/PDO/PgSQL/Driver.php
@@ -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;
@@ -16,6 +17,8 @@
final class Driver extends AbstractPostgreSQLDriver
{
+ use PDOConnect;
+
/**
* {@inheritDoc}
*/
@@ -39,7 +42,7 @@ public function connect(
unset($safeParams['password']);
try {
- $pdo = new PDO(
+ $pdo = $this->doConnect(
$this->constructPdoDsn($safeParams),
$params['user'] ?? '',
$params['password'] ?? '',
diff --git a/src/Driver/PDO/SQLSrv/Driver.php b/src/Driver/PDO/SQLSrv/Driver.php
index 7950c41a5e5..2d7490b8ae3 100644
--- a/src/Driver/PDO/SQLSrv/Driver.php
+++ b/src/Driver/PDO/SQLSrv/Driver.php
@@ -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;
@@ -19,6 +20,8 @@
final class Driver extends AbstractSQLServerDriver
{
+ use PDOConnect;
+
/**
* {@inheritDoc}
*/
@@ -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'] ?? '',
diff --git a/src/Driver/PDO/SQLite/Driver.php b/src/Driver/PDO/SQLite/Driver.php
index 4bef08bf997..fbd4187532d 100644
--- a/src/Driver/PDO/SQLite/Driver.php
+++ b/src/Driver/PDO/SQLite/Driver.php
@@ -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;
@@ -17,6 +17,8 @@
final class Driver extends AbstractSQLiteDriver
{
+ use PDOConnect;
+
/**
* {@inheritDoc}
*/
@@ -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'] ?? '',
diff --git a/src/Exception/InvalidColumnType/ColumnValuesRequired.php b/src/Exception/InvalidColumnType/ColumnValuesRequired.php
new file mode 100644
index 00000000000..ac6069ed30c
--- /dev/null
+++ b/src/Exception/InvalidColumnType/ColumnValuesRequired.php
@@ -0,0 +1,30 @@
+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.
*
@@ -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,
diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php
index d7fe9a790f8..53b71a8dcd5 100644
--- a/src/Platforms/AbstractPlatform.php
+++ b/src/Platforms/AbstractPlatform.php
@@ -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;
@@ -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;
@@ -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 $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.
*
diff --git a/src/Schema/Column.php b/src/Schema/Column.php
index 8963cd7acb2..2d02403b262 100644
--- a/src/Schema/Column.php
+++ b/src/Schema/Column.php
@@ -33,6 +33,9 @@ class Column extends AbstractAsset
protected bool $_autoincrement = false;
+ /** @var list */
+ protected array $_values = [];
+
/** @var array */
protected array $_platformOptions = [];
@@ -231,22 +234,41 @@ public function getComment(): string
return $this->_comment;
}
+ /**
+ * @param list $values
+ *
+ * @return $this
+ */
+ public function setValues(array $values): static
+ {
+ $this->_values = $values;
+
+ return $this;
+ }
+
+ /** @return list */
+ public function getValues(): array
+ {
+ return $this->_values;
+ }
+
/** @return array */
public function toArray(): array
{
return array_merge([
- 'name' => $this->_name,
- 'type' => $this->_type,
- 'default' => $this->_default,
- 'notnull' => $this->_notnull,
- 'length' => $this->_length,
- 'precision' => $this->_precision,
- 'scale' => $this->_scale,
- 'fixed' => $this->_fixed,
- 'unsigned' => $this->_unsigned,
- 'autoincrement' => $this->_autoincrement,
+ 'name' => $this->_name,
+ 'type' => $this->_type,
+ 'default' => $this->_default,
+ 'notnull' => $this->_notnull,
+ 'length' => $this->_length,
+ 'precision' => $this->_precision,
+ 'scale' => $this->_scale,
+ 'fixed' => $this->_fixed,
+ 'unsigned' => $this->_unsigned,
+ 'autoincrement' => $this->_autoincrement,
'columnDefinition' => $this->_columnDefinition,
- 'comment' => $this->_comment,
+ 'comment' => $this->_comment,
+ 'values' => $this->_values,
], $this->_platformOptions);
}
}
diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php
index 1c0915905fa..becb6819e8b 100644
--- a/src/Schema/MySQLSchemaManager.php
+++ b/src/Schema/MySQLSchemaManager.php
@@ -17,11 +17,13 @@
use Doctrine\DBAL\Types\Type;
use function array_change_key_case;
+use function array_map;
use function assert;
use function explode;
use function implode;
use function is_string;
use function preg_match;
+use function preg_match_all;
use function str_contains;
use function strtok;
use function strtolower;
@@ -134,6 +136,8 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
$type = $this->platform->getDoctrineTypeMapping($dbType);
+ $values = [];
+
switch ($dbType) {
case 'char':
case 'binary':
@@ -192,6 +196,10 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
case 'year':
$length = null;
break;
+
+ case 'enum':
+ $values = $this->parseEnumExpression($tableColumn['type']);
+ break;
}
if ($this->platform instanceof MariaDBPlatform) {
@@ -209,6 +217,7 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
'scale' => $scale,
'precision' => $precision,
'autoincrement' => str_contains($tableColumn['extra'], 'auto_increment'),
+ 'values' => $values,
];
if (isset($tableColumn['comment'])) {
@@ -228,6 +237,18 @@ protected function _getPortableTableColumnDefinition(array $tableColumn): Column
return $column;
}
+ /** @return list */
+ private function parseEnumExpression(string $expression): array
+ {
+ $result = preg_match_all("/'([^']*(?:''[^']*)*)'/", $expression, $matches);
+ assert($result !== false);
+
+ return array_map(
+ static fn (string $match): string => strtr($match, ["''" => "'"]),
+ $matches[1],
+ );
+ }
+
/**
* Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
*
diff --git a/src/Types/EnumType.php b/src/Types/EnumType.php
new file mode 100644
index 00000000000..489dc4b5c7d
--- /dev/null
+++ b/src/Types/EnumType.php
@@ -0,0 +1,18 @@
+getEnumDeclarationSQL($column);
+ }
+}
diff --git a/src/Types/Type.php b/src/Types/Type.php
index bc4d3aaf417..ee7797f0960 100644
--- a/src/Types/Type.php
+++ b/src/Types/Type.php
@@ -34,6 +34,7 @@ abstract class Type
Types::DATETIMETZ_MUTABLE => DateTimeTzType::class,
Types::DATETIMETZ_IMMUTABLE => DateTimeTzImmutableType::class,
Types::DECIMAL => DecimalType::class,
+ Types::ENUM => EnumType::class,
Types::FLOAT => FloatType::class,
Types::GUID => GuidType::class,
Types::INTEGER => IntegerType::class,
diff --git a/src/Types/Types.php b/src/Types/Types.php
index 6fef4cfce08..319218b021a 100644
--- a/src/Types/Types.php
+++ b/src/Types/Types.php
@@ -23,6 +23,7 @@ final class Types
public const DATETIMETZ_IMMUTABLE = 'datetimetz_immutable';
public const DECIMAL = 'decimal';
public const FLOAT = 'float';
+ public const ENUM = 'enum';
public const GUID = 'guid';
public const INTEGER = 'integer';
public const JSON = 'json';
diff --git a/tests/Functional/Driver/PDO/PDOSubclassTest.php b/tests/Functional/Driver/PDO/PDOSubclassTest.php
new file mode 100644
index 00000000000..5a9180ae383
--- /dev/null
+++ b/tests/Functional/Driver/PDO/PDOSubclassTest.php
@@ -0,0 +1,43 @@
+connection->getNativeConnection());
+ }
+
+ public function testPgSQLSubclass(): void
+ {
+ if (! TestUtil::isDriverOneOf('pdo_pgsql')) {
+ self::markTestSkipped('This test requires the pdo_pgsql driver.');
+ }
+
+ self::assertInstanceOf(Pgsql::class, $this->connection->getNativeConnection());
+ }
+
+ public function testSQLiteSubclass(): void
+ {
+ if (! TestUtil::isDriverOneOf('pdo_sqlite')) {
+ self::markTestSkipped('This test requires the pdo_sqlite driver.');
+ }
+
+ self::assertInstanceOf(Sqlite::class, $this->connection->getNativeConnection());
+ }
+}
diff --git a/tests/Functional/Schema/MySQLSchemaManagerTest.php b/tests/Functional/Schema/MySQLSchemaManagerTest.php
index 852eb555423..893f673ca3a 100644
--- a/tests/Functional/Schema/MySQLSchemaManagerTest.php
+++ b/tests/Functional/Schema/MySQLSchemaManagerTest.php
@@ -561,7 +561,10 @@ public function testColumnIntrospection(): void
$doctrineTypes = array_keys(Type::getTypesMap());
foreach ($doctrineTypes as $type) {
- $table->addColumn('col_' . $type, $type, ['length' => 8, 'precision' => 8, 'scale' => 2]);
+ $table->addColumn('col_' . $type, $type, match ($type) {
+ Types::ENUM => ['values' => ['foo', 'bar']],
+ default => ['length' => 8, 'precision' => 8, 'scale' => 2],
+ });
}
$this->dropAndCreateTable($table);
diff --git a/tests/Functional/Types/EnumTypeTest.php b/tests/Functional/Types/EnumTypeTest.php
new file mode 100644
index 00000000000..0f73b426d37
--- /dev/null
+++ b/tests/Functional/Types/EnumTypeTest.php
@@ -0,0 +1,132 @@
+dropTableIfExists('my_enum_table');
+ }
+
+ public function testIntrospectEnum(): void
+ {
+ if (! $this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
+ self::markTestSkipped('This test requires MySQL or MariaDB.');
+ }
+
+ $this->connection->executeStatement(<<< 'SQL'
+ CREATE TABLE my_enum_table (
+ id BIGINT NOT NULL PRIMARY KEY,
+ suit ENUM('hearts', 'diamonds', 'clubs', 'spades') NOT NULL DEFAULT 'hearts'
+ );
+ SQL);
+
+ $schemaManager = $this->connection->createSchemaManager();
+ $table = $schemaManager->introspectTable('my_enum_table');
+
+ self::assertCount(2, $table->getColumns());
+ self::assertTrue($table->hasColumn('suit'));
+ self::assertInstanceOf(EnumType::class, $table->getColumn('suit')->getType());
+ self::assertSame(['hearts', 'diamonds', 'clubs', 'spades'], $table->getColumn('suit')->getValues());
+ self::assertSame('hearts', $table->getColumn('suit')->getDefault());
+ }
+
+ public function testDeployEnum(): void
+ {
+ $schemaManager = $this->connection->createSchemaManager();
+ $schema = new Schema(schemaConfig: $schemaManager->createSchemaConfig());
+ $table = $schema->createTable('my_enum_table');
+ $table->addColumn('id', Types::BIGINT, ['notnull' => true]);
+ $table->addColumn('suit', Types::ENUM, [
+ 'values' => ['hearts', 'diamonds', 'clubs', 'spades'],
+ 'notnull' => true,
+ 'default' => 'hearts',
+ ]);
+ $table->setPrimaryKey(['id']);
+
+ $schemaManager->createSchemaObjects($schema);
+
+ $introspectedTable = $schemaManager->introspectTable('my_enum_table');
+
+ self::assertTrue($schemaManager->createComparator()->compareTables($table, $introspectedTable)->isEmpty());
+
+ $this->connection->insert('my_enum_table', ['id' => 1, 'suit' => 'hearts'], ['suit' => Types::ENUM]);
+ $this->connection->insert(
+ 'my_enum_table',
+ ['id' => 2, 'suit' => 'diamonds'],
+ ['suit' => Type::getType(Types::ENUM)],
+ );
+
+ self::assertEquals(
+ [[1, 'hearts'], [2, 'diamonds']],
+ $this->connection->fetchAllNumeric('SELECT id, suit FROM my_enum_table ORDER BY id ASC'),
+ );
+ }
+
+ public function testDeployEmptyEnum(): void
+ {
+ $schemaManager = $this->connection->createSchemaManager();
+ $schema = new Schema(schemaConfig: $schemaManager->createSchemaConfig());
+ $table = $schema->createTable('my_enum_table');
+ $table->addColumn('id', Types::BIGINT, ['notnull' => true]);
+ $table->addColumn('suit', Types::ENUM);
+ $table->setPrimaryKey(['id']);
+
+ $this->expectException(ColumnValuesRequired::class);
+
+ $schemaManager->createSchemaObjects($schema);
+ }
+
+ /** @param list $expectedValues */
+ #[DataProvider('provideEnumDefinitions')]
+ public function testIntrospectEnumValues(string $definition, array $expectedValues): void
+ {
+ if (! $this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
+ self::markTestSkipped('This test requires MySQL or MariaDB.');
+ }
+
+ $this->connection->executeStatement(<<< SQL
+ CREATE TABLE my_enum_table (
+ id BIGINT NOT NULL PRIMARY KEY,
+ my_enum $definition DEFAULT NULL
+ );
+ SQL);
+
+ $schemaManager = $this->connection->createSchemaManager();
+ $table = $schemaManager->introspectTable('my_enum_table');
+
+ self::assertInstanceOf(EnumType::class, $table->getColumn('my_enum')->getType());
+ self::assertSame($expectedValues, $table->getColumn('my_enum')->getValues());
+ self::assertNull($table->getColumn('my_enum')->getDefault());
+ }
+
+ /** @return iterable}> */
+ public static function provideEnumDefinitions(): iterable
+ {
+ yield 'simple' => ['ENUM("a", "b", "c")', ['a', 'b', 'c']];
+ yield 'empty first' => ['ENUM("", "a", "b", "c")', ['', 'a', 'b', 'c']];
+ yield 'empty in the middle' => ['ENUM("a", "", "b", "c")', ['a', '', 'b', 'c']];
+ yield 'empty last' => ['ENUM("a", "b", "c", "")', ['a', 'b', 'c', '']];
+ yield 'with spaces' => ['ENUM("a b", "c d", "e f")', ['a b', 'c d', 'e f']];
+ yield 'with quotes' => ['ENUM("a\'b", "c\'d", "e\'f")', ['a\'b', 'c\'d', 'e\'f']];
+ yield 'with commas' => ['ENUM("a,b", "c,d", "e,f")', ['a,b', 'c,d', 'e,f']];
+ yield 'with parentheses' => ['ENUM("(a)", "(b)", "(c)")', ['(a)', '(b)', '(c)']];
+ yield 'with quotes and commas' => ['ENUM("a\'b", "c\'d", "e\'f")', ['a\'b', 'c\'d', 'e\'f']];
+ yield 'with quotes and parentheses' => ['ENUM("(a)", "(b)", "(c)")', ['(a)', '(b)', '(c)']];
+ yield 'with commas and parentheses' => ['ENUM("(a,b)", "(c,d)", "(e,f)")', ['(a,b)', '(c,d)', '(e,f)']];
+ yield 'with quotes, commas and parentheses'
+ => ['ENUM("(a\'b)", "(c\'d)", "(e\'f)")', ['(a\'b)', '(c\'d)', '(e\'f)']];
+ }
+}
diff --git a/tests/Schema/ColumnTest.php b/tests/Schema/ColumnTest.php
index 4e99bba7ba2..c10bdd7be3e 100644
--- a/tests/Schema/ColumnTest.php
+++ b/tests/Schema/ColumnTest.php
@@ -52,6 +52,7 @@ public function testToArray(): void
'autoincrement' => false,
'columnDefinition' => null,
'comment' => '',
+ 'values' => [],
'foo' => 'bar',
];