TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
/**
* Provides a couple of methods use-full to restore Doctrine DBAL 3 and earlier behaviour to
* respect doctrine type override within column comment.
*
* Used within:
*
* - {@see MySQLSchemaManager::parentGetPortableTableColumnDefinition()}
* - {@see PostgreSQLSchemaManager::parentGetPortableTableColumnDefinition()}
* - {@see SQLiteSchemaManager::parentGetPortableTableColumnDefinition()}
*
* This trait contains code cloned or adopted from Doctrine DBAL 3 removed from {@see AbstractSchemaManager} hierarchy:
*
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1730-L1752
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1730-L1752
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/SqliteSchemaManager.php#L338-L344
*
* @internal for use in extended {@see AbstractSchemaManager} hierarchy and not part of public Core API.
*/
trait ColumnTypeCommentMethodsTrait
{
/**
* Determine Doctrine Type based on column database type with respecting
* comment definition as overrule type. That reflects the old behaviour
* of Doctrine DBAL 3 and older.
*
* Code has been adopted from Doctrine DBAL v3 to be used and integrated within extended {@see AbstractSchemaManager}
* hierarchy to restore removed behaviour, see:
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/MySQLSchemaManager.php#L186-L192
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/PostgreSQLSchemaManager.php#L427-L429
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/SqliteSchemaManager.php#L338-L344
*/
private function determineColumnType(string $dbType, array &$tableColumn): string
{
$platform = ($this instanceof AbstractPlatform) ? $this : $this->platform;
$type = $dbType !== '' ? $platform->getDoctrineTypeMapping($dbType) : '';
if (isset($tableColumn['comment'])) {
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
}
return $type;
}
/**
* Doctrine DBAL 4 removed this from the {@see AbstractSchemaManager} hierarchy, and is here cloned, see:
* https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1730-L1752
*
* Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
* the type given as default.
*
* @param string|null $comment
* @param string $currentType
*
* @return string
*@internal This method should be only used from within the extended AbstractSchemaManager class hierarchy.
*/
private function extractDoctrineTypeFromComment(?string $comment, string $currentType): string
{
if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match) === 1) {
return $match[1];
}
return $currentType;
}
/**
* Doctrine DBAL 4 removed this from the {@see AbstractSchemaManager} hierarchy, and is here cloned, see:
* https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/AbstractSchemaManager.php#L1754-L1773
*
* @param string|null $comment
* @param string|null $type
*
* @return string|null
*@internal This method should be only used from within the extended AbstractSchemaManager class hierarchy.
*/
private function removeDoctrineTypeFromComment(?string $comment = null, ?string $type = null): ?string
{
if ($comment === null) {
return null;
}
return str_replace('(DC2Type:' . $type . ')', '', $comment);
}
}
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform as DoctrineAbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform as DoctrineSQLitePlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SchemaManagerFactory;
/**
* Custom SchemaManager factory to ensure that the extended SchemaManager
* classes are used for supported platforms. Without this, custom schema
* handling would be cut off.
*
* Note: This is the transition to mitigate the dropped doctrine event manager
* since `doctrine/dbal ^4`.
*
* @see https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
* @see https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-schema-definition-events
*
* @internal for core internal usage and not part of public core API.
*/
final class CoreSchemaManagerFactory implements SchemaManagerFactory
{
public function createSchemaManager(Connection $connection): AbstractSchemaManager
{
$platform = $connection->getDatabasePlatform();
// Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
// do that to provide additional doctrine type handling and other workarounds or alignments. Long
// time this have been done by using the `doctrine EventManager` to hook into several places, which
// no longer exists.
//
// @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
// @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
// @todo Consider make check on SchemaManager instance retrieved from $platform->createSchemaManager()
return match (true) {
$platform instanceof DoctrineSQLitePlatform => new SQLiteSchemaManager($connection, $platform),
$platform instanceof DoctrinePostgreSQLPlatform => new PostgreSQLSchemaManager($connection, $platform),
$platform instanceof DoctrineMariaDBPlatform,
$platform instanceof DoctrineMySQLPlatform,
$platform instanceof DoctrineAbstractMySQLPlatform => new MySQLSchemaManager($connection, $platform),
default => $platform->createSchemaManager($connection),
};
}
}
@@ -0,0 +1,392 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\MariaDBPlatform;
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\MySQLSchemaManager as DoctrineMySQLSchemaManager;
use Doctrine\DBAL\Types\BlobType;
use Doctrine\DBAL\Types\JsonType;
use Doctrine\DBAL\Types\TextType;
use Doctrine\DBAL\Types\Type;
/**
* Extending the doctrine MySQLSchemaManager to integrate additional processing stuff
* due to the dropped event system with `doctrine/dbal 4.x`.
*
* For example, this is used to process custom doctrine types.
*
* Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
* do that to provide additional doctrine type handling and other workarounds or alignments. Long
* time this have been done by using the `doctrine EventManager` to hook into several places, which
* no longer exists.
*
* Note: MySQLSchemaManager is used for MySQL and MariaDB. Even doctrine/dbal 4.0 provides no dedicated
* schema manager for doctrine/dbal 4.0. Keep this in mind.
*
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
*
* @internal not part of the public Core API.
*/
class MySQLSchemaManager extends DoctrineMySQLSchemaManager
{
use ColumnTypeCommentMethodsTrait;
/** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */
private const MARIADB_ESCAPE_SEQUENCES = [
'\\0' => "\0",
"\\'" => "'",
'\\"' => '"',
'\\b' => "\b",
'\\n' => "\n",
'\\r' => "\r",
'\\t' => "\t",
'\\Z' => "\x1a",
'\\\\' => '\\',
'\\%' => '%',
'\\_' => '_',
// Internally, MariaDB escapes single quotes using the standard syntax
"''" => "'",
];
private const array MYSQL_ESCAPE_SEQUENCES = [
'\\0' => "\0",
"\\'" => "'",
'\\"' => '"',
'\\b' => "\b",
'\\n' => "\n",
'\\r' => "\r",
'\\t' => "\t",
'\\Z' => "\x1a",
'\\\\' => '\\',
'\\%' => '%',
'\\_' => '_',
// internally
"''" => "'",
];
private const array MYSQL_UNQUOTE_SEQUENCES = [
"\\'" => "'",
'\\"' => '"',
];
/**
* Gets Table Column Definition.
*
* @param array<string, mixed> $tableColumn
*/
protected function _getPortableTableColumnDefinition(array $tableColumn): Column
{
/** @var DoctrineMariaDBPlatform|DoctrineMySQLPlatform $platform */
$platform = $this->platform;
$tableColumn = $this->normalizeTableColumnData($tableColumn, $platform);
return $this->parentGetPortableTableColumnDefinition($tableColumn);
}
/**
* @param array<string, mixed> $tableColumn
* @return array<string, mixed>
*/
protected function normalizeTableColumnData(array $tableColumn, DoctrineMariaDBPlatform|DoctrineMySQLPlatform $platform): array
{
if (!($platform instanceof DoctrineMySQLPlatform)) {
return $tableColumn;
}
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
$dbType = strtolower($tableColumn['type']);
$columnDefault = $tableColumn['default'] ?? null;
$type = Type::getType($platform->getDoctrineTypeMapping($dbType));
if ($type instanceof TextType || $type instanceof BlobType || $type instanceof JsonType) {
$tableColumn['default'] = $this->getMySQLTextAndBlobColumnDefault($columnDefault);
}
return $tableColumn;
}
protected function getMySQLTextAndBlobColumnDefault(?string $columnDefault): ?string
{
if ($columnDefault === null || $columnDefault === 'NULL') {
return null;
}
if (str_starts_with($columnDefault, '_')) {
$columnDefault = substr($columnDefault, (mb_strpos($columnDefault, '\'') - 1));
}
if ($columnDefault === "\'\'") {
return '';
}
if (preg_match("/^\\\'(.*)\\\'$/", trim($columnDefault), $matches) === 1) {
return strtr(
strtr($matches[1], self::MYSQL_ESCAPE_SEQUENCES),
// MySQL saves quoted single-quote as escaped single-quote in the INFORMATION SCHEMA table, even
// if it has been provided with double-quote quoting and is inconsistent for itself and enforces
// a additional unquoting after the un-escaping step
self::MYSQL_UNQUOTE_SEQUENCES
);
}
return $columnDefault;
}
/**
* @param array<int, array<string, mixed>> $tableIndexes
* @param string $tableName
*
* @return array<string, Index>
*/
protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
{
// Get doctrine generated list.
$tableIndexesList = parent::_getPortableTableIndexesList(
// tableIndexes
$tableIndexes,
// tableName
$tableName,
);
// Concatenate index prefix length to column name
// @todo Adapt TYPO3 schema comparison to use Index::getOption('lengths')
// instead of assuming that the length is concatenated to the column name.
return array_map(
static function (Index $index): Index {
if (!$index->hasOption('lengths')) {
return $index;
}
$options = $index->getOptions();
$lengths = $options['lengths'];
unset($options['lengths']);
$columns = $index->getColumns();
foreach ($columns as $id => $column) {
if (!isset($lengths[$id])) {
continue;
}
$columns[$id] = $column . '(' . $lengths[$id] . ')';
}
return new Index(
$index->getName(),
$columns,
$index->isUnique(),
$index->isPrimary(),
$index->getFlags(),
$options
);
},
$tableIndexesList
);
}
/**
* Gets Table Column Definition.
*
* This is a copy of {@see DoctrineMySQLSchemaManager::_getPortableTableColumnDefinition()} with a minor change
* to respect column comments for Doctrine Type matching and thus restoring Doctrine DBAL behaviour before v4.x.
*
* @param array $tableColumn
*
* @throws Exception
*/
private function parentGetPortableTableColumnDefinition(array $tableColumn): Column
{
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
$dbType = $tableColumn['type'];
$length = null;
$scale = 0;
$precision = null;
$fixed = false;
$values = [];
// This is the change required for TYPO3 - rest of method is kept (cloned) from original.
// Following line differs from \Doctrine\DBAL\Schema\MySQLSchemaManager::_getPortableTableColumnDefinition,
// taken from:
// - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/MySQLSchemaManager.php#L186-L192
$type = $this->determineColumnType($dbType, $tableColumn);
switch ($dbType) {
case 'char':
case 'varchar':
$length = $tableColumn['character_maximum_length'];
break;
case 'binary':
case 'varbinary':
$length = $tableColumn['character_octet_length'];
break;
case 'tinytext':
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT;
break;
case 'text':
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT;
break;
case 'mediumtext':
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
break;
case 'tinyblob':
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB;
break;
case 'blob':
$length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB;
break;
case 'mediumblob':
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB;
break;
case 'float':
case 'double':
case 'real':
case 'numeric':
case 'decimal':
$precision = $tableColumn['numeric_precision'];
if (isset($tableColumn['numeric_scale'])) {
$scale = $tableColumn['numeric_scale'];
}
break;
}
switch ($dbType) {
case 'char':
case 'binary':
$fixed = true;
break;
case 'enum':
$values = $this->parseEnumExpression($tableColumn['column_type']);
break;
case 'set':
// --------------------------------------------------------------
// `SET` handling and parsing is a custom TYPO3 implementation
// --------------------------------------------------------------
$values = $this->parseSetExpression($tableColumn['column_type']);
// --------------------------------------------------------------
}
if ($this->platform instanceof MariaDBPlatform) {
$columnDefault = $this->getMariaDBColumnDefault($this->platform, $tableColumn['default']);
} else {
$columnDefault = $tableColumn['default'];
}
$options = [
'length' => $length,
'unsigned' => str_contains($tableColumn['column_type'], 'unsigned'),
'fixed' => $fixed,
'default' => $columnDefault,
'notnull' => $tableColumn['null'] !== 'YES',
'scale' => $scale,
'precision' => $precision,
'autoincrement' => str_contains($tableColumn['extra'], 'auto_increment'),
'values' => $values,
];
if (isset($tableColumn['comment'])) {
$options['comment'] = $tableColumn['comment'];
}
$column = new Column($tableColumn['field'], Type::getType($type), $options);
$column->setPlatformOption('charset', $tableColumn['characterset']);
$column->setPlatformOption('collation', $tableColumn['collation']);
return $column;
}
/**
* Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
*
* - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
* to distinguish them from expressions (see MDEV-10134).
* - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema
* as current_timestamp(), currdate(), currtime()
* - Quoted 'NULL' is not enforced by Maria, it is technically possible to have
* null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
* - \' is always stored as '' in information_schema (normalized)
*
* @link https://mariadb.com/kb/en/library/information-schema-columns-table/
* @link https://jira.mariadb.org/browse/MDEV-13132
*
* Copy of {@see DoctrineMySQLSchemaManager::getMariaDBColumnDefault()}
*
* @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
*/
private function getMariaDBColumnDefault(MariaDBPlatform $platform, ?string $columnDefault): ?string
{
if ($columnDefault === 'NULL' || $columnDefault === null) {
return null;
}
if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) {
return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
}
return match ($columnDefault) {
'current_timestamp()' => $platform->getCurrentTimestampSQL(),
'curdate()' => $platform->getCurrentDateSQL(),
'curtime()' => $platform->getCurrentTimeSQL(),
default => $columnDefault,
};
}
/**
* Cloned from {@see DoctrineMySQLSchemaManager::parseEnumExpression()} (4.3.x).
*
* @return list<string>
*/
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],
);
}
/**
* Adopted from {@see DoctrineMySQLSchemaManager::parseEnumExpression()} (4.3.x).
*
* @return list<string>
*/
private function parseSetExpression(string $expression): array
{
$result = preg_match_all("/'([^']*(?:''[^']*)*)'/", $expression, $matches);
assert($result !== false);
return array_map(
static fn(string $match): string => strtr($match, ["''" => "'"]),
$matches[1],
);
}
}
@@ -0,0 +1,200 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager as DoctrinePostgreSQLSchemaManager;
use Doctrine\DBAL\Types\JsonType;
use Doctrine\DBAL\Types\Type;
/**
* Extending the doctrine PostgreSQLSchemaManager to integrate additional processing stuff
* due to the dropped event system with `doctrine/dbal 4.x`.
*
* For example, this is used to process custom doctrine types.
*
* Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
* do that to provide additional doctrine type handling and other workarounds or alignments. Long
* time this have been done by using the `doctrine EventManager` to hook into several places, which
* no longer exists.
*
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
*
* @internal not part of the public Core API.
*/
class PostgreSQLSchemaManager extends DoctrinePostgreSQLSchemaManager
{
use ColumnTypeCommentMethodsTrait;
/**
* Gets Table Column Definition.
*
* @param array<string, mixed> $tableColumn
*/
protected function _getPortableTableColumnDefinition(array $tableColumn): Column
{
return $this->parentGetPortableTableColumnDefinition($tableColumn);
}
/**
* Gets Table Column Definition.
*
* This is a copy of {@see DoctrinePostgreSQLSchemaManager::_getPortableTableColumnDefinition()} with a minor change
* to respect column comments for Doctrine Type matching and thus restoring Doctrine DBAL behaviour before v4.x.
*
* @param array $tableColumn
*
* @throws Exception
*/
protected function parentGetPortableTableColumnDefinition(array $tableColumn): Column
{
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
$length = null;
$precision = null;
$scale = 0;
$fixed = false;
$jsonb = false;
$dbType = $tableColumn['type'];
if (
$tableColumn['domain_type'] !== null
&& ! $this->platform->hasDoctrineTypeMappingFor($dbType)
) {
$dbType = $tableColumn['domain_type'];
$completeType = $tableColumn['domain_complete_type'];
} else {
$completeType = $tableColumn['complete_type'];
}
// This is the change required for TYPO3 - rest of method is kept (cloned) from original.
// Following line differs from \Doctrine\DBAL\Schema\MySQLSchemaManager::_getPortableTableColumnDefinition,
// taken from:
// - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/PostgreSQLSchemaManager.php#L427-L429
$type = $this->determineColumnType($dbType, $tableColumn);
switch ($dbType) {
case 'bpchar':
case 'varchar':
$parameters = $this->parseColumnTypeParameters($completeType);
if (count($parameters) > 0) {
$length = $parameters[0];
}
break;
case 'double':
case 'decimal':
case 'money':
case 'numeric':
$parameters = $this->parseColumnTypeParameters($completeType);
if (count($parameters) > 0) {
$precision = $parameters[0];
}
if (count($parameters) > 1) {
$scale = $parameters[1];
}
break;
}
if ($dbType === 'bpchar') {
$fixed = true;
} elseif ($dbType === 'jsonb') {
$jsonb = true;
}
$options = [
'length' => $length,
'notnull' => (bool)$tableColumn['isnotnull'],
'default' => $this->parseDefaultExpression($tableColumn['default']),
'precision' => $precision,
'scale' => $scale,
'fixed' => $fixed,
'autoincrement' => $tableColumn['attidentity'] === 'd',
];
if ($tableColumn['comment'] !== null) {
$options['comment'] = $tableColumn['comment'];
}
$column = new Column($tableColumn['field'], Type::getType($type), $options);
if (! empty($tableColumn['collation'])) {
$column->setPlatformOption('collation', $tableColumn['collation']);
}
if ($column->getType() instanceof JsonType) {
$column->setPlatformOption('jsonb', $jsonb);
}
return $column;
}
/**
* Parses a default value expression as given by PostgreSQL
*
* Copy of {@see DoctrinePostgreSQLSchemaManager::parseDefaultExpression()} (Doctrine DBAL 4.3.x)
*/
private function parseDefaultExpression(?string $expression): mixed
{
if ($expression === null || str_starts_with($expression, 'NULL::')) {
return null;
}
if ($expression === 'true') {
return true;
}
if ($expression === 'false') {
return false;
}
if (preg_match("/^'(.*)'::/s", $expression, $matches) === 1) {
return str_replace("''", "'", $matches[1]);
}
return $expression;
}
/**
* Parses the parameters between parenthesis in the data type.
*
* Copy of {@see DoctrinePostgreSQLSchemaManager::parseColumnTypeParameters()}
*
* @return list<int>
*/
private function parseColumnTypeParameters(string $type): array
{
if (preg_match('/\((\d+)(?:,(\d+))?\)/', $type, $matches) !== 1) {
return [];
}
$parameters = [(int)$matches[1]];
if (isset($matches[2])) {
$parameters[] = (int)$matches[2];
}
return $parameters;
}
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Database\Schema\SchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\SQLiteSchemaManager as DoctrineSQLiteSchemaManager;
use Doctrine\DBAL\Types\Type;
/**
* Extending the doctrine SQLiteSchemaManager to integrate additional processing stuff
* due to the dropped event system with `doctrine/dbal 4.x`.
*
* For example, this is used to process custom doctrine types.
*
* Platform specific SchemaManager are extended to manipulate the schema handling. TYPO3 needs to
* do that to provide additional doctrine type handling and other workarounds or alignments. Long
* time this have been done by using the `doctrine EventManager` to hook into several places, which
* no longer exists.
*
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
* @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-extension-via-doctrine-event-manager
*
* @internal not part of the public Core API.
*/
class SQLiteSchemaManager extends DoctrineSQLiteSchemaManager
{
use ColumnTypeCommentMethodsTrait;
/**
* Doctrine DBAL v4 dropped column comment based type api, which TYPO3 still needs. To mitigate this, this
* method is overridden to reapply the type comment removal, adopted from:
*
* - https://github.com/doctrine/dbal/blob/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba/src/Schema/SqliteSchemaManager.php#L338-L344
*
* by using {@see ColumnTypeCommentMethodsTrait::determineColumnType()} to reuse methods.
*/
protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array
{
$list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
foreach ($list as $columnName => $column) {
$fakeTableColumn = [
'type' => $column->getType(),
'comment' => $column->getComment(),
];
$type = $this->determineColumnType('', $fakeTableColumn);
if ($type !== '') {
$column->setType(Type::getType($type));
}
$column->setComment($fakeTableColumn['comment']);
}
return $list;
}
/**
* Gets Table Column Definition.
*
* @param array<string, mixed> $tableColumn
*/
protected function _getPortableTableColumnDefinition(array $tableColumn): Column
{
return parent::_getPortableTableColumnDefinition($tableColumn);
}
}