diff --git a/composer.json b/composer.json index aafa9d73dc0b5eb2e9c295af1622bb88e09e5b2c..e2f61233d64c775e5e517ce81d4986df04bb67c7 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "bacon/bacon-qr-code": "^2.0.4", "christian-riesen/base32": "^1.6", "doctrine/annotations": "^1.11", - "doctrine/dbal": "^2.13.5", + "doctrine/dbal": "^3.2", "doctrine/event-manager": "^1.0.0", "doctrine/lexer": "^1.2.1", "egulias/email-validator": "^3.1", diff --git a/composer.lock b/composer.lock index 29bcf845df6cdf907bb7e20f9c36adddf2bb1136..fca81a469de37f3467b21a02585ac6f2a9102491 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2c78ea109eaae98bc8d0db996ec4e61e", + "content-hash": "c0bbf0d3a34fb321304fd8b0ce3434fc", "packages": [ { "name": "bacon/bacon-qr-code", @@ -118,6 +118,79 @@ }, "time": "2021-02-26T10:19:33+00:00" }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.4", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b174585d1fe49ceed21928a945138948cb394600" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-09-13T08:41:34+00:00" + }, { "name": "dasprid/enum", "version": "1.0.3", @@ -239,40 +312,39 @@ }, { "name": "doctrine/cache", - "version": "v1.8.1", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "d4374ae95b36062d02ef310100ed33d78738d76c" + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/d4374ae95b36062d02ef310100ed33d78738d76c", - "reference": "d4374ae95b36062d02ef310100ed33d78738d76c", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", "shasum": "" }, "require": { - "php": "~7.1" + "php": "~7.1 || ^8.0" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^4.0", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0", - "predis/predis": "~1.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" }, "suggest": { "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" @@ -304,49 +376,73 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "https://www.doctrine-project.org", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", "keywords": [ + "abstraction", + "apcu", "cache", - "caching" + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/v1.8.1" + "source": "https://github.com/doctrine/cache/tree/2.1.1" }, - "time": "2019-10-28T09:31:32+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" }, { "name": "doctrine/dbal", - "version": "2.13.5", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "d92ddb260547c2a7b650ff140f744eb6f395d8fc" + "reference": "5d54f63541d7bed1156cb5c9b79274ced61890e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/d92ddb260547c2a7b650ff140f744eb6f395d8fc", - "reference": "d92ddb260547c2a7b650ff140f744eb6f395d8fc", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5d54f63541d7bed1156cb5c9b79274ced61890e4", + "reference": "5d54f63541d7bed1156cb5c9b79274ced61890e4", "shasum": "" }, "require": { - "doctrine/cache": "^1.0|^2.0", + "composer/package-versions-deprecated": "^1.11.99", + "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3", "doctrine/event-manager": "^1.0", - "ext-pdo": "*", - "php": "^7.1 || ^8" + "php": "^7.3 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" }, "require-dev": { "doctrine/coding-standard": "9.0.0", "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.1.1", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.10", + "phpstan/phpstan": "1.2.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "9.5.10", "psalm/plugin-phpunit": "0.16.1", "squizlabs/php_codesniffer": "3.6.1", - "symfony/cache": "^4.4", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "4.12.0" + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.13.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -357,7 +453,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\DBAL\\": "lib/Doctrine/DBAL" + "Doctrine\\DBAL\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -400,14 +496,13 @@ "queryobject", "sasql", "sql", - "sqlanywhere", "sqlite", "sqlserver", "sqlsrv" ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.5" + "source": "https://github.com/doctrine/dbal/tree/3.2.0" }, "funding": [ { @@ -423,7 +518,7 @@ "type": "tidelift" } ], - "time": "2021-11-11T16:27:36+00:00" + "time": "2021-11-26T21:00:12+00:00" }, { "name": "doctrine/deprecations", @@ -5572,79 +5667,6 @@ }, "time": "2020-07-03T15:54:43+00:00" }, - { - "name": "composer/package-versions-deprecated", - "version": "1.11.99.4", - "source": { - "type": "git", - "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b174585d1fe49ceed21928a945138948cb394600" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", - "reference": "b174585d1fe49ceed21928a945138948cb394600", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.1.0 || ^2.0", - "php": "^7 || ^8" - }, - "replace": { - "ocramius/package-versions": "1.11.99" - }, - "require-dev": { - "composer/composer": "^1.9.3 || ^2.0@dev", - "ext-zip": "^1.13", - "phpunit/phpunit": "^6.5 || ^7" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "support": { - "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2021-09-13T08:41:34+00:00" - }, { "name": "composer/semver", "version": "3.2.5", diff --git a/typo3/sysext/backend/Classes/Authentication/PasswordReset.php b/typo3/sysext/backend/Classes/Authentication/PasswordReset.php index 4a95c85010b50f44559fedb68705370d4de2c01b..4d765aebdb22eaeeb282d478e95cebfdd3cfc40f 100644 --- a/typo3/sysext/backend/Classes/Authentication/PasswordReset.php +++ b/typo3/sysext/backend/Classes/Authentication/PasswordReset.php @@ -17,7 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Backend\Authentication; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; use Psr\Log\LoggerAwareInterface; @@ -285,7 +285,7 @@ class PasswordReset implements LoggerAwareInterface $queryBuilder ->select('uid', 'email', 'password_reset_token') ->from('be_users'); - if ($queryBuilder->getConnection()->getDatabasePlatform() instanceof MySqlPlatform) { + if ($queryBuilder->getConnection()->getDatabasePlatform() instanceof MySQLPlatform) { $queryBuilder->andWhere( $queryBuilder->expr()->comparison('SHA1(CONCAT(' . $queryBuilder->quoteIdentifier('email') . ', ' . $queryBuilder->quoteIdentifier('uid') . '))', $queryBuilder->expr()::EQ, $queryBuilder->createNamedParameter($identity)) ); diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php index a0cc5baca367a074129f87a1836ed34ad5384089..3ecffca875246baa49d78bbc1b78adbce4d69305 100644 --- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php +++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php @@ -429,8 +429,8 @@ class BackendUtility if (is_array($pageForRootlineCache[$ident] ?? false)) { $row = $pageForRootlineCache[$ident]; } else { - $statement = $runtimeCache->get('getPageForRootlineStatement-' . $statementCacheIdent); - if (!$statement) { + $queryBuilder = $runtimeCache->get('getPageForRootlineStatement-' . $statementCacheIdent); + if (!$queryBuilder) { $queryBuilder = static::getQueryBuilderForTable('pages'); $queryBuilder->getRestrictions() ->removeAll() @@ -466,17 +466,11 @@ class BackendUtility $queryBuilder->expr()->eq('uid', $queryBuilder->createPositionalParameter($uid, \PDO::PARAM_INT)), QueryHelper::stripLogicalOperatorPrefix($clause) ); - $statement = $queryBuilder->execute(); - if (class_exists(\Doctrine\DBAL\ForwardCompatibility\Result::class) && $statement instanceof \Doctrine\DBAL\ForwardCompatibility\Result) { - $statement = $statement->getIterator(); - } - $runtimeCache->set('getPageForRootlineStatement-' . $statementCacheIdent, $statement); + $runtimeCache->set('getPageForRootlineStatement-' . $statementCacheIdent, $queryBuilder); } else { - $statement->bindValue(1, (int)$uid); - $statement->execute(); + $queryBuilder->setParameter(0, (int)$uid); } - $row = $statement->fetchAssociative(); - $statement->free(); + $row = $queryBuilder->executeQuery()->fetchAssociative(); if ($row) { if ($workspaceOL) { diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php index 34a5c1a529a1048fef03d3af0d2516556003db97..f7048264ea17465c4b0967dea79e75ade0a9cbb7 100644 --- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php +++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php @@ -16,7 +16,7 @@ namespace TYPO3\CMS\Core\DataHandling; use Doctrine\DBAL\Exception as DBALException; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform; use Doctrine\DBAL\Types\IntegerType; use Psr\Log\LoggerAwareInterface; @@ -2374,16 +2374,13 @@ class DataHandler implements LoggerAwareInterface $newValue = $originalValue = $value; $queryBuilder = $this->getUniqueCountStatement($newValue, $table, $field, (int)$id, (int)$newPid); // For as long as records with the test-value existing, try again (with incremented numbers appended) - $statement = $queryBuilder->execute(); - if ($statement->fetchOne()) { + $result = $queryBuilder->executeQuery(); + if ($result->fetchOne()) { for ($counter = 0; $counter <= 100; $counter++) { $newValue = $value . $counter; - if (class_exists(\Doctrine\DBAL\ForwardCompatibility\Result::class) && $statement instanceof \Doctrine\DBAL\ForwardCompatibility\Result) { - $statement = $statement->getIterator(); - } - $statement->bindValue(1, $newValue); - $statement->execute(); - if (!$statement->fetchOne()) { + $queryBuilder->setParameter(0, $newValue); + $result = $queryBuilder->executeQuery(); + if (!$result->fetchOne()) { break; } } diff --git a/typo3/sysext/core/Classes/Database/Connection.php b/typo3/sysext/core/Classes/Database/Connection.php index 3a40ad989825e7f4816b530eb2728147a48ad399..27f4af430ca92981d5c5446cd6b6ef51e15f210e 100644 --- a/typo3/sysext/core/Classes/Database/Connection.php +++ b/typo3/sysext/core/Classes/Database/Connection.php @@ -20,12 +20,9 @@ namespace TYPO3\CMS\Core\Database; use Doctrine\Common\EventManager; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; use Doctrine\DBAL\Result; -use Doctrine\DBAL\Schema\AbstractSchemaManager; -use Doctrine\DBAL\VersionAwarePlatformDriver; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use TYPO3\CMS\Core\Database\Query\BulkInsertQuery; @@ -400,31 +397,14 @@ class Connection extends \Doctrine\DBAL\Connection implements LoggerAwareInterfa break; } - // Driver does not support version specific platforms. - if (!$this->getDriver() instanceof VersionAwarePlatformDriver) { - return $version; - } - - if ($this->getWrappedConnection() instanceof ServerInfoAwareConnection - && !$this->getWrappedConnection()->requiresQueryForServerVersion() - ) { + // if clause can be removed with Doctrine DBAL 4. + if (method_exists($this->getWrappedConnection(), 'getServerVersion')) { $version .= ' ' . $this->getWrappedConnection()->getServerVersion(); } return $version; } - /** - * Creates a SchemaManager that can be used to inspect or change the - * database schema through the connection. - * - * This is a copy from Doctrine DBAL 3.x, and can be removed once Doctrine DBAL 3.2 is included - */ - public function createSchemaManager(): AbstractSchemaManager - { - return $this->_driver->getSchemaManager($this); - } - /** * Execute commands after initializing a new connection. * diff --git a/typo3/sysext/core/Classes/Database/ConnectionPool.php b/typo3/sysext/core/Classes/Database/ConnectionPool.php index 36616f22afd80df345a2d9345c9a36eb27f1c001..1694f959e7cf971bb7813a33e00cb9233ac8c7a6 100644 --- a/typo3/sysext/core/Classes/Database/ConnectionPool.php +++ b/typo3/sysext/core/Classes/Database/ConnectionPool.php @@ -184,7 +184,6 @@ class ConnectionPool /** @var Connection $conn */ $conn = DriverManager::getConnection($connectionParams); - $conn->setFetchMode(\PDO::FETCH_ASSOC); $conn->prepareConnection($connectionParams['initCommands'] ?? ''); // Register custom data types diff --git a/typo3/sysext/core/Classes/Database/Driver/DriverConnection.php b/typo3/sysext/core/Classes/Database/Driver/DriverConnection.php new file mode 100644 index 0000000000000000000000000000000000000000..3f36a63f88a802695efd899f2dfc1c75c6c58ddc --- /dev/null +++ b/typo3/sysext/core/Classes/Database/Driver/DriverConnection.php @@ -0,0 +1,111 @@ +<?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\Driver; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\PDO\Connection as DoctrineDbalPDOConnection; +use Doctrine\DBAL\Driver\PDO\Exception; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; +use PDO; + +/** + * This is a full "clone" of the class of package doctrine/dbal. Scope is to use instanatiate TYPO3's DriverResult + * and DriverStatement objects instead of Doctrine's native implementation. + * + * @internal this implementation is not part of TYPO3's Public API. + */ +class DriverConnection implements ConnectionInterface, ServerInfoAwareConnection +{ + protected DoctrineDbalPDOConnection $doctrineDbalPDOConnection; + protected PDO $connection; + + public function __construct(PDO $connection) + { + $this->connection = $connection; + $this->doctrineDbalPDOConnection = new DoctrineDbalPDOConnection($connection); + } + + public function exec(string $sql): int + { + return $this->doctrineDbalPDOConnection->exec($sql); + } + + public function getServerVersion() + { + return $this->doctrineDbalPDOConnection->getServerVersion(); + } + + public function prepare(string $sql): StatementInterface + { + try { + $stmt = $this->connection->prepare($sql); + assert($stmt instanceof \PDOStatement); + + // use TYPO3's Statement object in favor of Doctrine's Statement wrapper + return new DriverStatement($stmt); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + public function query(string $sql): ResultInterface + { + try { + $stmt = $this->connection->query($sql); + assert($stmt instanceof \PDOStatement); + + // use TYPO3's Result object in favor of Doctrine's Result wrapper + return new DriverResult($stmt); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + public function quote($value, $type = ParameterType::STRING) + { + return $this->doctrineDbalPDOConnection->quote($value, $type); + } + + public function lastInsertId($name = null) + { + return $this->doctrineDbalPDOConnection->lastInsertId($name); + } + + public function beginTransaction(): bool + { + return $this->doctrineDbalPDOConnection->beginTransaction(); + } + + public function commit(): bool + { + return $this->doctrineDbalPDOConnection->commit(); + } + + public function rollBack(): bool + { + return $this->doctrineDbalPDOConnection->rollBack(); + } + + public function getWrappedConnection(): PDO + { + return $this->doctrineDbalPDOConnection->getWrappedConnection(); + } +} diff --git a/typo3/sysext/core/Classes/Database/Driver/DriverResult.php b/typo3/sysext/core/Classes/Database/Driver/DriverResult.php new file mode 100644 index 0000000000000000000000000000000000000000..4a8b93b9e53d57f95f237a6bdb16da2caf0395ae --- /dev/null +++ b/typo3/sysext/core/Classes/Database/Driver/DriverResult.php @@ -0,0 +1,172 @@ +<?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\Driver; + +use Doctrine\DBAL\Driver\PDO\Exception; +use Doctrine\DBAL\Driver\Result as ResultInterface; + +/** + * TYPO3's custom Result object for Database statements based on Doctrine DBAL. + * + * This is a lowlevel wrapper around PDO for TYPO3 based drivers to ensure mapResourceToString() + * is called when retrieving data. This isn't the actual Result object (Doctrine\DBAL\Result) which + * is used in user-land code. + * + * Because Doctrine's DBAL Driver Result object is marked as final, all logic is copied from the ResultInterface. + * + * @internal this implementation is not part of TYPO3's Public API. + */ +class DriverResult implements ResultInterface +{ + private \PDOStatement $statement; + + /** + * @internal The result can be only instantiated by its driver connection or statement. + */ + public function __construct(\PDOStatement $statement) + { + $this->statement = $statement; + } + + /** + * {@inheritDoc} + */ + public function fetchNumeric() + { + return $this->fetch(\PDO::FETCH_NUM); + } + + /** + * {@inheritDoc} + */ + public function fetchAssociative() + { + return $this->fetch(\PDO::FETCH_ASSOC); + } + + /** + * {@inheritDoc} + */ + public function fetchOne() + { + return $this->fetch(\PDO::FETCH_COLUMN); + } + + /** + * {@inheritDoc} + */ + public function fetchAllNumeric(): array + { + return $this->fetchAll(\PDO::FETCH_NUM); + } + + /** + * {@inheritDoc} + */ + public function fetchAllAssociative(): array + { + return $this->fetchAll(\PDO::FETCH_ASSOC); + } + + /** + * {@inheritDoc} + */ + public function fetchFirstColumn(): array + { + return $this->fetchAll(\PDO::FETCH_COLUMN); + } + + public function rowCount(): int + { + try { + return $this->statement->rowCount(); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + public function columnCount(): int + { + try { + return $this->statement->columnCount(); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + public function free(): void + { + $this->statement->closeCursor(); + } + + /** + * @return mixed|false + * + * @throws Exception + */ + private function fetch(int $mode) + { + try { + $result = $this->statement->fetch($mode); + $result = $this->mapResourceToString($result); + return $result; + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * @return list<mixed> + * + * @throws Exception + */ + private function fetchAll(int $mode): array + { + try { + $data = $this->statement->fetchAll($mode); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + + assert(is_array($data)); + return array_map([$this, 'mapResourceToString'], $data); + } + + /** + * Map resources to string like is done for e.g. in mysqli driver + * + * @param mixed $record + * @return mixed + */ + protected function mapResourceToString($record) + { + if (is_array($record)) { + return array_map( + static function ($value) { + if (is_resource($value)) { + $value = stream_get_contents($value); + } + return $value; + }, + $record + ); + } + + return $record; + } +} diff --git a/typo3/sysext/core/Classes/Database/Driver/DriverStatement.php b/typo3/sysext/core/Classes/Database/Driver/DriverStatement.php new file mode 100644 index 0000000000000000000000000000000000000000..98075ac43807a83769c46d490a6c5629d8dae5c0 --- /dev/null +++ b/typo3/sysext/core/Classes/Database/Driver/DriverStatement.php @@ -0,0 +1,139 @@ +<?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\Driver; + +use Doctrine\DBAL\Driver\Exception as ExceptionInterface; +use Doctrine\DBAL\Driver\Exception\UnknownParameterType; +use Doctrine\DBAL\Driver\PDO\Exception; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; +use Doctrine\Deprecations\Deprecation; + +/** + * TYPO3's custom Statement object for Database statements based on Doctrine DBAL in TYPO3's drivers. + * + * This is a lowlevel wrapper around PDOStatement for TYPO3 based drivers to ensure the PDOStatement is put into + * TYPO3's DriverResult object, and not in Doctrine's Result object. If Doctrine DBAL had a factory + * for DriverResults this class could be removed. + * + * Because Doctrine's DBAL Driver PDO-Statement object is marked as final, all logic is copied from that class. + * + * @internal this implementation is not part of TYPO3's Public API. + */ +class DriverStatement implements StatementInterface +{ + private const PARAM_TYPE_MAP = [ + ParameterType::NULL => \PDO::PARAM_NULL, + ParameterType::INTEGER => \PDO::PARAM_INT, + ParameterType::STRING => \PDO::PARAM_STR, + ParameterType::ASCII => \PDO::PARAM_STR, + ParameterType::BINARY => \PDO::PARAM_LOB, + ParameterType::LARGE_OBJECT => \PDO::PARAM_LOB, + ParameterType::BOOLEAN => \PDO::PARAM_BOOL, + ]; + + /** @var \PDOStatement */ + private $stmt; + + /** + * @internal The statement can be only instantiated by its driver connection. + */ + public function __construct(\PDOStatement $stmt) + { + $this->stmt = $stmt; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = ParameterType::STRING) + { + $type = $this->convertParamType($type); + + try { + return $this->stmt->bindValue($param, $value, $type); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritDoc} + * + * @param mixed $param + * @param mixed $variable + * @param int $type + * @param int|null $length + * @param mixed $driverOptions The usage of the argument is deprecated. + */ + public function bindParam( + $param, + &$variable, + $type = ParameterType::STRING, + $length = null, + $driverOptions = null + ): bool { + if (func_num_args() > 4) { + Deprecation::triggerIfCalledFromOutside( + 'doctrine/dbal', + 'https://github.com/doctrine/dbal/issues/4533', + 'The $driverOptions argument of Statement::bindParam() is deprecated.' + ); + } + + $type = $this->convertParamType($type); + + try { + return $this->stmt->bindParam($param, $variable, $type, ...array_slice(func_get_args(), 3)); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + } + + /** + * {@inheritdoc} + */ + public function execute($params = null): ResultInterface + { + try { + $this->stmt->execute($params); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } + + // use TYPO3's Result object in favor of Doctrine's Result wrapper + return new DriverResult($this->stmt); + } + + /** + * Converts DBAL parameter type to PDO parameter type + * + * @param int $type Parameter type + * + * @throws ExceptionInterface + */ + private function convertParamType(int $type): int + { + if (! isset(self::PARAM_TYPE_MAP[$type])) { + throw UnknownParameterType::new($type); + } + + return self::PARAM_TYPE_MAP[$type]; + } +} diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOConnection.php b/typo3/sysext/core/Classes/Database/Driver/PDOConnection.php deleted file mode 100644 index aee45f6ace5c4801f4a295bcf2083add216aad9d..0000000000000000000000000000000000000000 --- a/typo3/sysext/core/Classes/Database/Driver/PDOConnection.php +++ /dev/null @@ -1,42 +0,0 @@ -<?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\Driver; - -use Doctrine\DBAL\Driver\PDO\Connection as DoctrineDbalPDOConnection; -use Doctrine\DBAL\Driver\PDO\Exception as PDOException; -use PDO; - -/** - * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. - * All private methods have to be checked on every release of doctrine/dbal. - */ -class PDOConnection extends DoctrineDbalPDOConnection -{ - /** - * {@inheritdoc} - */ - public function __construct($dsn, $user = null, $password = null, ?array $options = null) - { - try { - parent::__construct($dsn, $user, $password, $options); - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [PDOStatement::class, []]); - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } -} diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOMySql/Driver.php b/typo3/sysext/core/Classes/Database/Driver/PDOMySql/Driver.php index c9d6edca6c695a3430d6ab061183cf73584afcc0..da655c72138016557404d169ca2288ca19924e3a 100644 --- a/typo3/sysext/core/Classes/Database/Driver/PDOMySql/Driver.php +++ b/typo3/sysext/core/Classes/Database/Driver/PDOMySql/Driver.php @@ -18,38 +18,51 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Driver\PDOMySql; use Doctrine\DBAL\Driver\AbstractMySQLDriver; -use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Driver\Connection as DriverConnectionInterface; +use Doctrine\DBAL\Driver\PDO\Exception; +use PDO; use PDOException; -use TYPO3\CMS\Core\Database\Driver\PDOConnection; +use TYPO3\CMS\Core\Database\Driver\DriverConnection; /** - * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. + * The main change in favor of Doctrine's implementation is to use our custom DriverConnection (which in turn creates + * a custom Result object). + * * All private methods have to be checked on every release of doctrine/dbal. + * + * @internal this implementation is not part of TYPO3's Public API. */ class Driver extends AbstractMySQLDriver { /** * {@inheritdoc} + * + * @return DriverConnectionInterface */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params) { + $driverOptions = $params['driverOptions'] ?? []; + + if (! empty($params['persistent'])) { + $driverOptions[PDO::ATTR_PERSISTENT] = true; + } + try { - $conn = new PDOConnection( + $pdo = new PDO( $this->constructPdoDsn($params), - $username, - $password, + $params['user'] ?? '', + $params['password'] ?? '', $driverOptions ); - // use prepared statements for pdo_mysql per default to retrieve native data types if (!isset($driverOptions[\PDO::ATTR_EMULATE_PREPARES])) { - $conn->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); + $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); } - } catch (PDOException $e) { - throw DBALException::driverException($this, $e); + } catch (PDOException $exception) { + throw Exception::new($exception); } - return $conn; + return new DriverConnection($pdo); } /** diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOPgSql/Driver.php b/typo3/sysext/core/Classes/Database/Driver/PDOPgSql/Driver.php index 4a6e19c6f836c3cf2c1c4bcb9f47b8fffd0654a1..ba4b061c7f513d91e511710f26fa93696ab99089 100644 --- a/typo3/sysext/core/Classes/Database/Driver/PDOPgSql/Driver.php +++ b/typo3/sysext/core/Classes/Database/Driver/PDOPgSql/Driver.php @@ -18,28 +18,39 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Driver\PDOPgSql; use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver; -use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Driver\Connection as DriverConnectionInterface; +use Doctrine\DBAL\Driver\PDO\Exception; use PDO; use PDOException; -use TYPO3\CMS\Core\Database\Driver\PDOConnection; +use TYPO3\CMS\Core\Database\Driver\DriverConnection; /** - * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. - * All private methods have to be checked on every release of doctrine/dbal. + * The main change in favor of Doctrine's implementation is to use our custom DriverConnection (which in turn creates + * a custom Result object). + * + * @internal this implementation is not part of TYPO3's Public API. */ class Driver extends AbstractPostgreSQLDriver { /** * {@inheritdoc} + * + * @return DriverConnectionInterface */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params) { + $driverOptions = $params['driverOptions'] ?? []; + + if (! empty($params['persistent'])) { + $driverOptions[PDO::ATTR_PERSISTENT] = true; + } + try { - $pdo = new PDOConnection( + $pdo = new PDO( $this->_constructPdoDsn($params), - $username, - $password, - $driverOptions + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions, ); if (defined('PDO::PGSQL_ATTR_DISABLE_PREPARES') @@ -58,11 +69,11 @@ class Driver extends AbstractPostgreSQLDriver if (isset($params['charset'])) { $pdo->exec('SET NAMES \'' . $params['charset'] . '\''); } - - return $pdo; - } catch (PDOException $e) { - throw DBALException::driverException($this, $e); + } catch (PDOException $exception) { + throw Exception::new($exception); } + + return new DriverConnection($pdo); } /** diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOSqlite/Driver.php b/typo3/sysext/core/Classes/Database/Driver/PDOSqlite/Driver.php index 12f6593fc2f47fc48eabb89e106fd2d65cf9b8f8..c1c3c1e0c0520eaca0e02a4fa5b16e05440c5fd4 100644 --- a/typo3/sysext/core/Classes/Database/Driver/PDOSqlite/Driver.php +++ b/typo3/sysext/core/Classes/Database/Driver/PDOSqlite/Driver.php @@ -18,14 +18,18 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Driver\PDOSqlite; use Doctrine\DBAL\Driver\AbstractSQLiteDriver; -use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Driver\Connection as DriverConnectionInterface; +use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Platforms\SqlitePlatform; +use PDO; use PDOException; -use TYPO3\CMS\Core\Database\Driver\PDOConnection; +use TYPO3\CMS\Core\Database\Driver\DriverConnection as TYPO3DriverConnection; /** - * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. - * All private methods have to be checked on every release of doctrine/dbal. + * The main change in favor of Doctrine's implementation is to use our custom DriverConnection (which in turn creates + * a custom Result object). + * + * @internal this implementation is not part of TYPO3's Public API. */ class Driver extends AbstractSQLiteDriver { @@ -40,9 +44,13 @@ class Driver extends AbstractSQLiteDriver /** * {@inheritdoc} + * + * @return DriverConnectionInterface */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params) { + $driverOptions = $params['driverOptions'] ?? []; + if (isset($driverOptions['userDefinedFunctions'])) { $this->_userDefinedFunctions = array_merge( $this->_userDefinedFunctions, @@ -52,21 +60,21 @@ class Driver extends AbstractSQLiteDriver } try { - $pdo = new PDOConnection( + $pdo = new PDO( $this->_constructPdoDsn($params), - $username, - $password, + $params['user'] ?? '', + $params['password'] ?? '', $driverOptions ); - } catch (PDOException $ex) { - throw DBALException::driverException($this, $ex); + } catch (PDOException $exception) { + throw Exception::new($exception); } foreach ($this->_userDefinedFunctions as $fn => $data) { $pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']); } - return $pdo; + return new TYPO3DriverConnection($pdo); } /** diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Connection.php b/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Connection.php index 61c674cdb019237226474a3faf249faf6b85d821..5ff23ead550c3e969181278351edd2ba6da4b451 100644 --- a/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Connection.php +++ b/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Connection.php @@ -17,26 +17,29 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Driver\PDOSqlsrv; -use Doctrine\DBAL\Driver\Result; -use PDO; +use Doctrine\DBAL\Driver\PDO\Exception; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use TYPO3\CMS\Core\Database\Driver\DriverConnection; /** * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. - * All private methods have to be checked on every release of doctrine/dbal. + * + * @internal this implementation is not part of TYPO3's Public API. */ -class Connection extends \Doctrine\DBAL\Driver\PDO\Connection +class Connection extends DriverConnection { - /** - * @internal The connection can be only instantiated by its driver. - * - * {@inheritdoc} - */ - public function __construct($dsn, $user = null, $password = null, ?array $options = null) + public function prepare(string $sql): StatementInterface { - parent::__construct($dsn, $user, $password, $options); - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, [Statement::class, []]); + try { + $stmt = $this->connection->prepare($sql); + assert($stmt instanceof \PDOStatement); + + // use TYPO3's Sqlsrv Statement object in favor of Doctrine's Statement wrapper + return new Statement($stmt); + } catch (\PDOException $exception) { + throw Exception::new($exception); + } } - /** * {@inheritDoc} */ @@ -47,12 +50,7 @@ class Connection extends \Doctrine\DBAL\Driver\PDO\Connection } $stmt = $this->prepare('SELECT CONVERT(VARCHAR(MAX), current_value) FROM sys.sequences WHERE name = ?'); - $stmt->execute([$name]); - - if ($stmt instanceof Result) { - return $stmt->fetchOne(); - } - - return $stmt->fetchColumn(); + $result = $stmt->execute([$name]); + return $result->fetchOne(); } } diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Driver.php b/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Driver.php index 4e2220a885db03ca673f9a0d7f9da49a8fe57ab8..7afaac4e6e9c525eebcf7b4ab55b39a179d3f65e 100644 --- a/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Driver.php +++ b/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Driver.php @@ -19,26 +19,54 @@ namespace TYPO3\CMS\Core\Database\Driver\PDOSqlsrv; use Doctrine\DBAL\Driver\AbstractSQLServerDriver; use Doctrine\DBAL\Driver\AbstractSQLServerDriver\Exception\PortWithoutHost; +use Doctrine\DBAL\Driver\Connection as DriverConnection; +use Doctrine\DBAL\Driver\PDO\Exception as PDOException; /** - * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. + * The main change in favor of Doctrine's implementation is to use our custom DriverConnection (which in turn creates + * a custom Result object). + * * All private methods have to be checked on every release of doctrine/dbal. + * + * @internal this implementation is not part of TYPO3's Public API. */ class Driver extends AbstractSQLServerDriver { /** * {@inheritdoc} + * + * @return DriverConnection */ - public function connect(array $params, $username = null, $password = null, array $driverOptions = []) + public function connect(array $params) { - [$driverOptions, $connectionOptions] = $this->splitOptions($driverOptions); - - return new Connection( - $this->_constructPdoDsn($params, $connectionOptions), - $username, - $password, - $driverOptions - ); + $driverOptions = $dsnOptions = []; + + if (isset($params['driverOptions'])) { + foreach ($params['driverOptions'] as $option => $value) { + if (is_int($option)) { + $driverOptions[$option] = $value; + } else { + $dsnOptions[$option] = $value; + } + } + } + + if (!empty($params['persistent'])) { + $driverOptions[\PDO::ATTR_PERSISTENT] = true; + } + + try { + $pdo = new \PDO( + $this->_constructPdoDsn($params, $dsnOptions), + $params['user'] ?? '', + $params['password'] ?? '', + $driverOptions + ); + } catch (\PDOException $exception) { + throw PDOException::new($exception); + } + + return new Connection($pdo); } /** @@ -74,22 +102,6 @@ class Driver extends AbstractSQLServerDriver return $dsn . $this->getConnectionOptionsDsn($connectionOptions); } - private function splitOptions(array $options): array - { - $driverOptions = []; - $connectionOptions = []; - - foreach ($options as $optionKey => $optionValue) { - if (is_int($optionKey)) { - $driverOptions[$optionKey] = $optionValue; - } else { - $connectionOptions[$optionKey] = $optionValue; - } - } - - return [$driverOptions, $connectionOptions]; - } - /** * Converts a connection options array to the DSN * diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Statement.php b/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Statement.php index ebec0cdcf007b79959ea59dd5fdcbe7c8759a356..7181c25e6807b3d432ea729300e8f23125c345c0 100644 --- a/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Statement.php +++ b/typo3/sysext/core/Classes/Database/Driver/PDOSqlsrv/Statement.php @@ -18,27 +18,39 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Driver\PDOSqlsrv; use Doctrine\DBAL\ParameterType; -use PDO; -use TYPO3\CMS\Core\Database\Driver\PDOStatement; +use TYPO3\CMS\Core\Database\Driver\DriverStatement; /** * This is a full "clone" of the class of package doctrine/dbal. Scope is to use the PDOConnection of TYPO3. * All private methods have to be checked on every release of doctrine/dbal. + * + * @internal this implementation is not part of TYPO3's Public API. */ -class Statement extends PDOStatement +class Statement extends DriverStatement { /** - * {@inheritdoc} + * {@inheritDoc} + * + * @param mixed $param + * @param mixed $variable + * @param int $type + * @param int|null $length + * @param mixed $driverOptions The usage of the argument is deprecated. */ - public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) - { + public function bindParam( + $param, + &$variable, + $type = ParameterType::STRING, + $length = null, + $driverOptions = null + ): bool { if (($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY) && $driverOptions === null ) { - $driverOptions = PDO::SQLSRV_ENCODING_BINARY; + $driverOptions = \PDO::SQLSRV_ENCODING_BINARY; } - return parent::bindParam($column, $variable, $type, $length, $driverOptions); + return parent::bindParam($param, $variable, $type, $length, $driverOptions); } /** diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOStatement.php b/typo3/sysext/core/Classes/Database/Driver/PDOStatement.php deleted file mode 100644 index aa94b0312eb88c98ec60c9a57d8bc24f30db6819..0000000000000000000000000000000000000000 --- a/typo3/sysext/core/Classes/Database/Driver/PDOStatement.php +++ /dev/null @@ -1,91 +0,0 @@ -<?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\Driver; - -use Doctrine\DBAL\Driver\PDO\Exception as PDOException; -use Doctrine\DBAL\Driver\PDO\Statement as DoctrineDbalPDOStatement; -use PDO; - -class PDOStatement extends DoctrineDbalPDOStatement -{ - /** - * The method fetchAll() is moved into a separate trait to switch method signatures - * depending on the PHP major version in use to support PHP8 - */ - use PDOStatementImplementation; - - /** - * Map resources to string like is done for e.g. in mysqli driver - * - * @param mixed $record - * @return mixed - */ - protected function mapResourceToString($record) - { - if (is_array($record)) { - return array_map( - static function ($value) { - if (is_resource($value)) { - $value = stream_get_contents($value); - } - - return $value; - }, - $record - ); - } - - return $record; - } - - /** - * {@inheritdoc} - */ - public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - try { - $record = parent::fetch($fetchMode, $cursorOrientation, $cursorOffset); - $record = $this->mapResourceToString($record); - return $record; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function fetchOne($columnIndex = 0) - { - try { - $record = parent::fetchColumn($columnIndex); - $record = $this->mapResourceToString($record); - return $record; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - - /** - * {@inheritdoc} - */ - public function fetchColumn($columnIndex = 0) - { - return $this->fetchOne($columnIndex); - } -} diff --git a/typo3/sysext/core/Classes/Database/Driver/PDOStatementImplementation.php b/typo3/sysext/core/Classes/Database/Driver/PDOStatementImplementation.php deleted file mode 100644 index 2bda74a5f9bf47145d1f12d9bf56592403dc5928..0000000000000000000000000000000000000000 --- a/typo3/sysext/core/Classes/Database/Driver/PDOStatementImplementation.php +++ /dev/null @@ -1,64 +0,0 @@ -<?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\Driver; - -use Doctrine\DBAL\Driver\PDO\Exception as PDOException; - -if (PHP_VERSION_ID >= 80000) { - trait PDOStatementImplementation - { - /** - * {@inheritdoc} - */ - public function fetchAll($mode = null, ...$args) - { - try { - $records = parent::fetchAll($mode, ...$args); - - if ($records !== false) { - $records = array_map([$this, 'mapResourceToString'], $records); - } - - return $records; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - } -} else { - trait PDOStatementImplementation - { - /** - * {@inheritdoc} - */ - public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) - { - try { - $records = parent::fetchAll($fetchMode, $fetchArgument, $ctorArgs); - - if ($records !== false) { - $records = array_map([$this, 'mapResourceToString'], $records); - } - - return $records; - } catch (\PDOException $exception) { - throw new PDOException($exception); - } - } - } -} diff --git a/typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php b/typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php index 736bcb3e83bb1ce138f8a7194e25684724cd989b..b59744874235a565446340640cf9e1647a612133 100644 --- a/typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php +++ b/typo3/sysext/core/Classes/Database/Platform/PlatformInformation.php @@ -19,8 +19,8 @@ namespace TYPO3\CMS\Core\Database\Platform; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySqlPlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform; @@ -143,7 +143,7 @@ class PlatformInformation */ protected static function getPlatformIdentifier(AbstractPlatform $platform): string { - if ($platform instanceof MySqlPlatform) { + if ($platform instanceof MySQLPlatform) { return 'mysql'; } if ($platform instanceof PostgreSqlPlatform) { diff --git a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php index 26e12868d6c320d9528be6c224cbaa9b49bc5b13..7d620c7440c8abdd0b40357960abb99716b1e7f2 100644 --- a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php +++ b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php @@ -17,10 +17,9 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Query; -use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform; use Doctrine\DBAL\Query\Expression\CompositeExpression; @@ -217,8 +216,7 @@ class QueryBuilder * executeStatement() for 'INSERT', 'UPDATE' and 'DELETE' queries. * * @return Result|int - * @throws DBALException - * @todo Deprecate in v12 along with raise to min doctrine/dbal:^3.2 to align with doctrine/dbal deprecation. + * @internal use executeQuery() and executeStatement() instead. Only here for backwards-compatibility */ public function execute() { @@ -240,22 +238,20 @@ class QueryBuilder * versions and avoid deprecation warning. Additional this will ease * backport without the need to switch if execute() is not used anymore. * - * @throws DBALException + * @return Result */ public function executeQuery(): Result { // Set additional query restrictions $originalWhereConditions = $this->addAdditionalWhereConditions(); - // @todo Call $this->concreteQueryBuilder->executeQuery() - // directly with doctrine/dbal:^3.2 raise in v12. - $return = $this->concreteQueryBuilder->execute(); + $result = $this->concreteQueryBuilder->executeQuery(); // Restore the original query conditions in case the user keeps // on modifying the state. $this->concreteQueryBuilder->add('where', $originalWhereConditions, false); - return $return; + return $result; } /** @@ -271,14 +267,10 @@ class QueryBuilder * backport without the need to switch if execute() is not used anymore. * * @return int The number of affected rows. - * - * @throws DBALException */ public function executeStatement(): int { - // @todo Call $this->concreteQueryBuilder->executeStatement() - // directly with doctrine/dbal:^3.2 raise in v12. - return $this->concreteQueryBuilder->execute(); + return $this->concreteQueryBuilder->executeStatement(); } /** @@ -1182,7 +1174,7 @@ class QueryBuilder { $databasePlatform = $this->connection->getDatabasePlatform(); // https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#function_convert - if ($databasePlatform instanceof MySqlPlatform) { + if ($databasePlatform instanceof MySQLPlatform) { return sprintf('CONVERT(%s, CHAR)', $this->connection->quoteIdentifier($fieldName)); } // https://www.postgresql.org/docs/current/sql-createcast.html diff --git a/typo3/sysext/core/Classes/Database/Schema/Comparator.php b/typo3/sysext/core/Classes/Database/Schema/Comparator.php index 60a4f0de1c8377fe5c55e4d72ae2b3a0b612c01f..8807439647e9b19c41b39bcb37a04a34b7748e0c 100644 --- a/typo3/sysext/core/Classes/Database/Schema/Comparator.php +++ b/typo3/sysext/core/Classes/Database/Schema/Comparator.php @@ -18,13 +18,17 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Schema; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\SchemaDiff; +use Doctrine\DBAL\Schema\SchemaException; +use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\BlobType; use Doctrine\DBAL\Types\TextType; use TYPO3\CMS\Core\Utility\ArrayUtility; -use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Compares two Schemas and returns an instance of SchemaDiff. @@ -46,6 +50,7 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator public function __construct(AbstractPlatform $platform = null) { $this->databasePlatform = $platform; + parent::__construct($platform); } /** @@ -70,14 +75,13 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator } if ($tableDifferences === false) { - $tableDifferences = GeneralUtility::makeInstance(TableDiff::class, $fromTable->getName()); + $tableDifferences = new TableDiff($fromTable->getName()); $tableDifferences->fromTable = $fromTable; } else { $renamedColumns = $tableDifferences->renamedColumns; $renamedIndexes = $tableDifferences->renamedIndexes; // Rebuild TableDiff with enhanced TYPO3 TableDiff class - $tableDifferences = GeneralUtility::makeInstance( - TableDiff::class, + $tableDifferences = new TableDiff( $tableDifferences->name, $tableDifferences->addedColumns, $tableDifferences->changedColumns, @@ -111,7 +115,7 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator $changedProperties = parent::diffColumn($column1, $column2); // Only MySQL has variable length versions of TEXT/BLOB - if (!$this->databasePlatform instanceof MySqlPlatform) { + if (!$this->databasePlatform instanceof MySQLPlatform) { return $changedProperties; } @@ -130,4 +134,162 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator return array_unique($changedProperties); } + + /** + * Returns a SchemaDiff object containing the differences between the schemas + * $fromSchema and $toSchema. + * + * This method should be called non-statically since it will be declared as + * non-static in the next doctrine/dbal major release. + * + * doctrine/dbal uses new self() in this method, which instantiate the doctrine + * Comparator class and not our extended class, thus not calling our overridden + * methods 'diffTable()' and 'diffColum()' and breaking core table engine support, + * which is tested in core functional tests explicity. See corresponding test + * `TYPO3\CMS\Core\Tests\Functional\Database\Schema\SchemaMigratorTest->changeTableEngine()` + * + * @return SchemaDiff + * @throws SchemaException + * + * @todo Create PR for doctrine/dbal to change to late binding 'new static()', so our + * override is working correctly and remove this method after min package raise, + * if PR was accepted and merged. Also remove 'isAutoIncrementSequenceInSchema()'. + */ + public static function compareSchemas( + Schema $fromSchema, + Schema $toSchema + ) { + $comparator = new self(); + $diff = new SchemaDiff(); + $diff->fromSchema = $fromSchema; + + $foreignKeysToTable = []; + + foreach ($toSchema->getNamespaces() as $namespace) { + if ($fromSchema->hasNamespace($namespace)) { + continue; + } + + $diff->newNamespaces[$namespace] = $namespace; + } + + foreach ($fromSchema->getNamespaces() as $namespace) { + if ($toSchema->hasNamespace($namespace)) { + continue; + } + + $diff->removedNamespaces[$namespace] = $namespace; + } + + foreach ($toSchema->getTables() as $table) { + $tableName = $table->getShortestName($toSchema->getName()); + if (! $fromSchema->hasTable($tableName)) { + $diff->newTables[$tableName] = $toSchema->getTable($tableName); + } else { + $tableDifferences = $comparator->diffTable( + $fromSchema->getTable($tableName), + $toSchema->getTable($tableName) + ); + + if ($tableDifferences !== false) { + $diff->changedTables[$tableName] = $tableDifferences; + } + } + } + + /* Check if there are tables removed */ + foreach ($fromSchema->getTables() as $table) { + $tableName = $table->getShortestName($fromSchema->getName()); + + $table = $fromSchema->getTable($tableName); + if (! $toSchema->hasTable($tableName)) { + $diff->removedTables[$tableName] = $table; + } + + // also remember all foreign keys that point to a specific table + foreach ($table->getForeignKeys() as $foreignKey) { + $foreignTable = strtolower($foreignKey->getForeignTableName()); + if (! isset($foreignKeysToTable[$foreignTable])) { + $foreignKeysToTable[$foreignTable] = []; + } + + $foreignKeysToTable[$foreignTable][] = $foreignKey; + } + } + + foreach ($diff->removedTables as $tableName => $table) { + if (! isset($foreignKeysToTable[$tableName])) { + continue; + } + + $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]); + + // deleting duplicated foreign keys present on both on the orphanedForeignKey + // and the removedForeignKeys from changedTables + foreach ($foreignKeysToTable[$tableName] as $foreignKey) { + // strtolower the table name to make if compatible with getShortestName + $localTableName = strtolower($foreignKey->getLocalTableName()); + if (! isset($diff->changedTables[$localTableName])) { + continue; + } + + foreach ($diff->changedTables[$localTableName]->removedForeignKeys as $key => $removedForeignKey) { + assert($removedForeignKey instanceof ForeignKeyConstraint); + + // We check if the key is from the removed table if not we skip. + if ($tableName !== strtolower($removedForeignKey->getForeignTableName())) { + continue; + } + + unset($diff->changedTables[$localTableName]->removedForeignKeys[$key]); + } + } + } + + foreach ($toSchema->getSequences() as $sequence) { + $sequenceName = $sequence->getShortestName($toSchema->getName()); + if (! $fromSchema->hasSequence($sequenceName)) { + if (! $comparator->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) { + $diff->newSequences[] = $sequence; + } + } else { + if ($comparator->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { + $diff->changedSequences[] = $toSchema->getSequence($sequenceName); + } + } + } + + foreach ($fromSchema->getSequences() as $sequence) { + if ($comparator->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { + continue; + } + + $sequenceName = $sequence->getShortestName($fromSchema->getName()); + + if ($toSchema->hasSequence($sequenceName)) { + continue; + } + + $diff->removedSequences[] = $sequence; + } + + return $diff; + } + + /** + * @param Schema $schema + * @param Sequence $sequence + * @todo Remove this method, when 'compareSchemas()' could removed. We needed to borrow + * this method along with 'compareSchemas()' through missing late-static binding. + */ + private function isAutoIncrementSequenceInSchema($schema, $sequence): bool + { + foreach ($schema->getTables() as $table) { + if ($sequence->isAutoIncrementsFor($table)) { + return true; + } + } + + return false; + } } diff --git a/typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php b/typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php index d885b8d766551c605ca0ec1bc50ad6db3d47afbc..20ba6f5b9b2ddb6c9b3c98f96ded4f7bbe5e6daa 100644 --- a/typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php +++ b/typo3/sysext/core/Classes/Database/Schema/ConnectionMigrator.php @@ -18,8 +18,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Schema; use Doctrine\DBAL\Exception as DBALException; -use Doctrine\DBAL\Platforms\MySqlPlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform; use Doctrine\DBAL\Schema\Column; @@ -150,11 +150,10 @@ class ConnectionMigrator // existing columns as false positives for a column rename. In this // context every rename is actually a new column. foreach ($changedTable->renamedColumns as $columnName => $renamedColumn) { - $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance( - Column::class, + $changedTable->addedColumns[$renamedColumn->getName()] = new Column( $renamedColumn->getName(), $renamedColumn->getType(), - array_diff_key($renamedColumn->toArray(), ['name', 'type']) + $this->prepareColumnOptions($renamedColumn) ); unset($changedTable->renamedColumns[$columnName]); } @@ -232,7 +231,7 @@ class ConnectionMigrator // Build SchemaDiff and handle renames of tables and columns $comparator = GeneralUtility::makeInstance(Comparator::class, $this->connection->getDatabasePlatform()); - $schemaDiff = $comparator->compare($fromSchema, $toSchema); + $schemaDiff = $comparator->compareSchemas($fromSchema, $toSchema); $schemaDiff = $this->migrateColumnRenamesToDistinctActions($schemaDiff); if ($renameUnused) { @@ -248,7 +247,7 @@ class ConnectionMigrator // If there are no mapped tables return a SchemaDiff without any changes // to avoid update suggestions for tables not related to TYPO3. if (empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'] ?? null)) { - return GeneralUtility::makeInstance(SchemaDiff::class, [], [], [], $fromSchema); + return new SchemaDiff([], [], [], $fromSchema); } // Collect the table names that have been mapped to this connection. @@ -304,21 +303,21 @@ class ConnectionMigrator $tableName, array_merge($currentTableDefinition->getColumns(), $table->getColumns()), array_merge($currentTableDefinition->getIndexes(), $table->getIndexes()), + [], array_merge($currentTableDefinition->getForeignKeys(), $table->getForeignKeys()), - 0, array_merge($currentTableDefinition->getOptions(), $table->getOptions()) ); } $tablesForConnection = $this->transformTablesForDatabasePlatform($tablesForConnection, $this->connection); - $schemaConfig = GeneralUtility::makeInstance(SchemaConfig::class); + $schemaConfig = new SchemaConfig(); $schemaConfig->setName($this->connection->getDatabase()); if (isset($this->connection->getParams()['tableoptions'])) { $schemaConfig->setDefaultTableOptions($this->connection->getParams()['tableoptions']); } - return GeneralUtility::makeInstance(Schema::class, $tablesForConnection, [], $schemaConfig); + return new Schema($tablesForConnection, [], $schemaConfig); } /** @@ -332,8 +331,7 @@ class ConnectionMigrator protected function getNewTableUpdateSuggestions(SchemaDiff $schemaDiff): array { // Build a new schema diff that only contains added tables - $addTableSchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $addTableSchemaDiff = new SchemaDiff( $schemaDiff->newTables, [], [], @@ -365,8 +363,7 @@ class ConnectionMigrator // Treat each added column with a new diff to get a dedicated suggestions // just for this single column. foreach ($changedTable->addedColumns as $columnName => $addedColumn) { - $changedTables[$index . ':tbl_' . $addedColumn->getName()] = GeneralUtility::makeInstance( - TableDiff::class, + $changedTables[$index . ':tbl_' . $addedColumn->getName()] = new TableDiff( $changedTable->name, [$columnName => $addedColumn], [], @@ -383,8 +380,7 @@ class ConnectionMigrator // Treat each added index with a new diff to get a dedicated suggestions // just for this index. foreach ($changedTable->addedIndexes as $indexName => $addedIndex) { - $changedTables[$index . ':idx_' . $addedIndex->getName()] = GeneralUtility::makeInstance( - TableDiff::class, + $changedTables[$index . ':idx_' . $addedIndex->getName()] = new TableDiff( $changedTable->name, [], [], @@ -402,8 +398,7 @@ class ConnectionMigrator // just for this foreign key. foreach ($changedTable->addedForeignKeys as $addedForeignKey) { $fkIndex = $index . ':fk_' . $addedForeignKey->getName(); - $changedTables[$fkIndex] = GeneralUtility::makeInstance( - TableDiff::class, + $changedTables[$fkIndex] = new TableDiff( $changedTable->name, [], [], @@ -419,8 +414,7 @@ class ConnectionMigrator } // Build a new schema diff that only contains added fields - $addFieldSchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $addFieldSchemaDiff = new SchemaDiff( [], $changedTables, [], @@ -464,8 +458,7 @@ class ConnectionMigrator ); $tableOptionsDiff->setTableOptions($tableOptions); - $tableOptionsSchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $tableOptionsSchemaDiff = new SchemaDiff( [], [$tableOptionsDiff], [], @@ -500,8 +493,7 @@ class ConnectionMigrator // just for this index. if (count($changedTable->changedIndexes) !== 0) { foreach ($changedTable->changedIndexes as $indexName => $changedIndex) { - $indexDiff = GeneralUtility::makeInstance( - TableDiff::class, + $indexDiff = new TableDiff( $changedTable->name, [], [], @@ -512,8 +504,7 @@ class ConnectionMigrator $schemaDiff->fromSchema->getTable($changedTable->name) ); - $temporarySchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $temporarySchemaDiff = new SchemaDiff( [], [$indexDiff], [], @@ -531,8 +522,7 @@ class ConnectionMigrator if (count($changedTable->renamedIndexes) !== 0) { // Create a base table diff without any changes, there's no constructor // argument to pass in renamed indexes. - $tableDiff = GeneralUtility::makeInstance( - TableDiff::class, + $tableDiff = new TableDiff( $changedTable->name, [], [], @@ -549,8 +539,7 @@ class ConnectionMigrator $indexDiff = clone $tableDiff; $indexDiff->renamedIndexes = [$key => $renamedIndex]; - $temporarySchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $temporarySchemaDiff = new SchemaDiff( [], [$indexDiff], [], @@ -587,8 +576,7 @@ class ConnectionMigrator ); // Build a dedicated diff just for the current column - $tableDiff = GeneralUtility::makeInstance( - TableDiff::class, + $tableDiff = new TableDiff( $changedTable->name, [], [$columnName => $changedColumn], @@ -599,8 +587,7 @@ class ConnectionMigrator $fromTable ); - $temporarySchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $temporarySchemaDiff = new SchemaDiff( [], [$tableDiff], [], @@ -618,8 +605,7 @@ class ConnectionMigrator // Treat each changed foreign key with a new diff to get a dedicated suggestions // just for this foreign key. if (count($changedTable->changedForeignKeys) !== 0) { - $tableDiff = GeneralUtility::makeInstance( - TableDiff::class, + $tableDiff = new TableDiff( $changedTable->name, [], [], @@ -634,8 +620,7 @@ class ConnectionMigrator $foreignKeyDiff = clone $tableDiff; $foreignKeyDiff->changedForeignKeys = [$this->buildQuotedForeignKey($changedForeignKey)]; - $temporarySchemaDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $temporarySchemaDiff = new SchemaDiff( [], [$foreignKeyDiff], [], @@ -676,8 +661,7 @@ class ConnectionMigrator continue; } // Build a new schema diff that only contains this table - $changedFieldDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $changedFieldDiff = new SchemaDiff( [], [$tableDiff], [], @@ -725,8 +709,7 @@ class ConnectionMigrator continue; } - $renameColumnTableDiff = GeneralUtility::makeInstance( - TableDiff::class, + $renameColumnTableDiff = new TableDiff( $changedTable->name, [], [$oldFieldName => $changedColumn], @@ -748,8 +731,7 @@ class ConnectionMigrator } // Build a new schema diff that only contains unused fields - $changedFieldDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $changedFieldDiff = new SchemaDiff( [], $changedTables, [], @@ -786,8 +768,7 @@ class ConnectionMigrator // Treat each changed column with a new diff to get a dedicated suggestions // just for this single column. foreach ($changedTable->removedColumns as $columnName => $removedColumn) { - $changedTables[$index . ':tbl_' . $removedColumn->getName()] = GeneralUtility::makeInstance( - TableDiff::class, + $changedTables[$index . ':tbl_' . $removedColumn->getName()] = new TableDiff( $changedTable->name, [], [], @@ -808,8 +789,7 @@ class ConnectionMigrator // Treat each removed index with a new diff to get a dedicated suggestions // just for this index. foreach ($changedTable->removedIndexes as $indexName => $removedIndex) { - $changedTables[$index . ':idx_' . $removedIndex->getName()] = GeneralUtility::makeInstance( - TableDiff::class, + $changedTables[$index . ':idx_' . $removedIndex->getName()] = new TableDiff( $changedTable->name, [], [], @@ -834,8 +814,7 @@ class ConnectionMigrator continue; } $fkIndex = $index . ':fk_' . $removedForeignKey->getName(); - $changedTables[$fkIndex] = GeneralUtility::makeInstance( - TableDiff::class, + $changedTables[$fkIndex] = new TableDiff( $changedTable->name, [], [], @@ -854,8 +833,7 @@ class ConnectionMigrator } // Build a new schema diff that only contains removable fields - $removedFieldDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $removedFieldDiff = new SchemaDiff( [], $changedTables, [], @@ -883,8 +861,7 @@ class ConnectionMigrator $updateSuggestions = []; foreach ($schemaDiff->removedTables as $removedTable) { // Build a new schema diff that only contains this table - $tableDiff = GeneralUtility::makeInstance( - SchemaDiff::class, + $tableDiff = new SchemaDiff( [], [], [$this->buildQuotedTable($removedTable)], @@ -923,8 +900,7 @@ class ConnectionMigrator if (strpos($removedTable->getName(), $this->deletedPrefix) === 0) { continue; } - $tableDiff = GeneralUtility::makeInstance( - TableDiff::class, + $tableDiff = new TableDiff( $removedTable->getQuotedName($this->connection->getDatabasePlatform()), [], // added columns [], // changed columns @@ -979,12 +955,11 @@ class ConnectionMigrator $renamedColumn = new Column( $this->connection->quoteIdentifier($renamedColumnName), $removedColumn->getType(), - array_diff_key($removedColumn->toArray(), ['name', 'type']) + $this->prepareColumnOptions($removedColumn) ); // Build the diff object for the column to rename - $columnDiff = GeneralUtility::makeInstance( - ColumnDiff::class, + $columnDiff = new ColumnDiff( $removedColumn->getQuotedName($this->connection->getDatabasePlatform()), $renamedColumn, [], // changed properties @@ -1021,16 +996,14 @@ class ConnectionMigrator // Treat each renamed column with a new diff to get a dedicated // suggestion just for this single column. foreach ($changedTable->renamedColumns as $originalColumnName => $renamedColumn) { - $columnOptions = array_diff_key($renamedColumn->toArray(), ['name', 'type']); + $columnOptions = $this->prepareColumnOptions($renamedColumn); - $changedTable->addedColumns[$renamedColumn->getName()] = GeneralUtility::makeInstance( - Column::class, + $changedTable->addedColumns[$renamedColumn->getName()] = new Column( $renamedColumn->getName(), $renamedColumn->getType(), $columnOptions ); - $changedTable->removedColumns[$originalColumnName] = GeneralUtility::makeInstance( - Column::class, + $changedTable->removedColumns[$originalColumnName] = new Column( $originalColumnName, $renamedColumn->getType(), $columnOptions @@ -1146,7 +1119,7 @@ class ConnectionMigrator // Remove the length information from column names for indexes if required. $cleanedColumnNames = array_map( static function (string $columnName) use ($connection) { - if ($connection->getDatabasePlatform() instanceof MySqlPlatform) { + if ($connection->getDatabasePlatform() instanceof MySQLPlatform) { // Returning the unquoted, unmodified version of the column name since // it can include the length information for BLOB/TEXT columns which // may not be quoted. @@ -1158,8 +1131,7 @@ class ConnectionMigrator $index->getUnquotedColumns() ); - $indexes[$key] = GeneralUtility::makeInstance( - Index::class, + $indexes[$key] = new Index( $connection->quoteIdentifier($indexName), $cleanedColumnNames, $index->isUnique(), @@ -1173,8 +1145,8 @@ class ConnectionMigrator $table->getQuotedName($connection->getDatabasePlatform()), $table->getColumns(), $indexes, + [], $table->getForeignKeys(), - 0, array_merge($defaultTableOptions, $table->getOptions()) ); } @@ -1192,7 +1164,7 @@ class ConnectionMigrator protected function getTableOptions(array $tableNames): array { $tableOptions = []; - if (strpos($this->connection->getServerVersion(), 'MySQL') !== 0) { + if (!$this->connection->getDatabasePlatform() instanceof MySQLPlatform) { foreach ($tableNames as $tableName) { $tableOptions[$tableName] = []; } @@ -1258,8 +1230,8 @@ class ConnectionMigrator $databasePlatform->quoteIdentifier($table->getName()), $table->getColumns(), $table->getIndexes(), + [], $table->getForeignKeys(), - 0, $table->getOptions() ); } @@ -1277,11 +1249,10 @@ class ConnectionMigrator { $databasePlatform = $this->connection->getDatabasePlatform(); - return GeneralUtility::makeInstance( - Column::class, + return new Column( $databasePlatform->quoteIdentifier($column->getName()), $column->getType(), - array_diff_key($column->toArray(), ['name', 'type']) + $this->prepareColumnOptions($column) ); } @@ -1298,8 +1269,7 @@ class ConnectionMigrator { $databasePlatform = $this->connection->getDatabasePlatform(); - return GeneralUtility::makeInstance( - Index::class, + return new Index( $databasePlatform->quoteIdentifier($index->getName()), $index->getColumns(), $index->isUnique(), @@ -1322,8 +1292,7 @@ class ConnectionMigrator { $databasePlatform = $this->connection->getDatabasePlatform(); - return GeneralUtility::makeInstance( - ForeignKeyConstraint::class, + return new ForeignKeyConstraint( $index->getLocalColumns(), $databasePlatform->quoteIdentifier($index->getForeignTableName()), $index->getForeignColumns(), @@ -1332,6 +1301,29 @@ class ConnectionMigrator ); } + protected function prepareColumnOptions(Column $column): array + { + $options = $column->toArray(); + $platformOptions = $column->getPlatformOptions(); + foreach ($platformOptions as $optionName => $optionValue) { + unset($options[$optionName]); + if (!isset($options['platformOptions'])) { + $options['platformOptions'] = []; + } + $options['platformOptions'][$optionName] = $optionValue; + } + $schemaOptions = $column->getCustomSchemaOptions(); + foreach ($schemaOptions as $optionName => $optionValue) { + unset($options[$optionName]); + if (!isset($options['schemaOptions'])) { + $options['schemaOptions'] = []; + } + $options['schemaOptions'][$optionName] = $optionValue; + } + unset($options['name'], $options['type']); + return $options; + } + protected function getDatabasePlatform(string $tableName): string { $databasePlatform = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName)->getDatabasePlatform(); diff --git a/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaAlterTableListener.php b/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaAlterTableListener.php index f33935ac6cc66fb592213ecf7eb400a29b0a3b28..30318397072df799605a98c82f8a83c4fe017acf 100644 --- a/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaAlterTableListener.php +++ b/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaAlterTableListener.php @@ -18,7 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Schema\EventListener; use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use TYPO3\CMS\Core\Database\Schema\TableDiff; /** @@ -46,7 +46,7 @@ class SchemaAlterTableListener } // Table options are only supported on MySQL, continue default processing - if (!$event->getPlatform() instanceof MySqlPlatform) { + if (!$event->getPlatform() instanceof MySQLPlatform) { return false; } diff --git a/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php b/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php index 63930931bf657199dc310863967af0f1193a434d..a318faf4ea53980744a641f66590d3730ebda493 100644 --- a/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php +++ b/typo3/sysext/core/Classes/Database/Schema/EventListener/SchemaIndexDefinitionListener.php @@ -18,7 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Schema\EventListener; use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\Index; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -40,7 +40,7 @@ class SchemaIndexDefinitionListener public function onSchemaIndexDefinition(SchemaIndexDefinitionEventArgs $event) { // Early return for non-MySQL-compatible platforms - if (!($event->getConnection()->getDatabasePlatform() instanceof MySqlPlatform)) { + if (!($event->getConnection()->getDatabasePlatform() instanceof MySQLPlatform)) { return; } diff --git a/typo3/sysext/core/Classes/Database/Schema/Parser/TableBuilder.php b/typo3/sysext/core/Classes/Database/Schema/Parser/TableBuilder.php index df70dbd15fd893c604c1e23f1959ed01052dd562..b95755af4a8c1b4424348d73678dbfab822e0bf6 100644 --- a/typo3/sysext/core/Classes/Database/Schema/Parser/TableBuilder.php +++ b/typo3/sysext/core/Classes/Database/Schema/Parser/TableBuilder.php @@ -18,7 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Database\Schema\Parser; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Table; @@ -101,7 +101,7 @@ class TableBuilder Type::addType($type, $className); } } - $this->platform = $platform ?: GeneralUtility::makeInstance(MySqlPlatform::class); + $this->platform = $platform ?: GeneralUtility::makeInstance(MySQLPlatform::class); } /** @@ -121,7 +121,7 @@ class TableBuilder [], [], [], - 0, + [], $this->buildTableOptions($tableStatement->tableOptions) ); @@ -260,8 +260,8 @@ class TableBuilder $this->table->getQuotedName($this->platform), $this->table->getColumns(), array_merge($this->table->getIndexes(), [strtolower($indexName) => $index]), + [], $this->table->getForeignKeys(), - 0, $this->table->getOptions() ); } diff --git a/typo3/sysext/core/Classes/Database/Schema/SchemaMigrator.php b/typo3/sysext/core/Classes/Database/Schema/SchemaMigrator.php index 5ec699c9f7ab4b80fc242ee91403881db4d44840..5c404e4a399b0cbfd2555383b97935d6a67035fe 100644 --- a/typo3/sysext/core/Classes/Database/Schema/SchemaMigrator.php +++ b/typo3/sysext/core/Classes/Database/Schema/SchemaMigrator.php @@ -138,7 +138,7 @@ class SchemaMigrator $connection = $connectionPool->getConnectionByName($connectionName); foreach ($statementsToExecute as $hash => $statement) { try { - $connection->executeUpdate($statement); + $connection->executeStatement($statement); } catch (DBALException $e) { $result[$hash] = $e->getPrevious()->getMessage(); } @@ -288,8 +288,8 @@ class SchemaMigrator $table->getName(), array_merge($prioritizedColumns, $nonPrioritizedColumns), $table->getIndexes(), + [], $table->getForeignKeys(), - 0, $table->getOptions() ); } diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96287-DoctrineDBAL32.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96287-DoctrineDBAL32.rst new file mode 100644 index 0000000000000000000000000000000000000000..1a3352b7beb41cc7cddee0f8e6f6ef9cda7ad63a --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-96287-DoctrineDBAL32.rst @@ -0,0 +1,75 @@ +.. include:: ../../Includes.txt + +==================================== +Breaking: #96287 - Doctrine DBAL 3.2 +==================================== + +See :issue:`96287` + +Description +=========== + +TYPO3 v12.0 has updated its Database Abstraction package based on Doctrine +DBAL to the next major version Doctrine DBAL v3. + + +Impact +====== + +Doctrine DBAL 3 has undergone major refactorings internally by separating +Doctrine's internal driver logic from PHP's native PDO functionality. + +See https://www.doctrine-project.org/2021/03/29/dbal-2.13.html and +https://www.doctrine-project.org/2020/11/17/dbal-3.0.0.html +for more details. + +In addition, most database APIs which TYPO3 provides as wrappers around +the existing functionality is already available in TYPO3 v11 and +continue to work in TYPO3 v12. + + + +Affected Installations +====================== + +TYPO3 installations with custom third-party extensions using TYPO3's +Database Abstraction functionality, or extensions using +the Doctrine DBAL API directly. + + +Migration +========= + +Read Doctrine's migration paths (see links above) to migrate any existing +code. + +The main change for 95% of the developers are, that queries and database result-sets +now have more explicit APIs when querying the database. + +Examples: + +.. code-block:: php + + $result = $queryBuilder + ->select(...) + ->from(...) + // use executeQuery() instead of execute() + ->executeQuery(); + +`$result` is now of type `\Doctrine\DBAL\Result`, and not of type +`\Doctrine\DBAL\Statement` anymore, which allows to fetch rows / columns via +new and more speaking methods: + +* ->fetchAllAssociative() instead of ->fetchAll() +* ->fetchAssociative() - instead of ->fetch() +* ->fetchOne() - instead of ->fetchColumn(0) + +The method `executeQuery` - available in the QueryBuilder and +the Connection class is now in for select/count queries and returns a Result +object directly, whereas `executeStatement()` is used for insert / update / delete +statements, returning an integer - the number of affected rows. + +Use both methods instead of the previous `execute()` method, +which is still available for backwards-compatibility. + +.. index:: Database, FullyScanned, ext:core diff --git a/typo3/sysext/core/Tests/Unit/Database/ConnectionTest.php b/typo3/sysext/core/Tests/Unit/Database/ConnectionTest.php index 886837cc37c4447b7a574304301a9d54d06fcd14..6ce6f495b4b315d39f654277ff55404c0179d7bd 100644 --- a/typo3/sysext/core/Tests/Unit/Database/ConnectionTest.php +++ b/typo3/sysext/core/Tests/Unit/Database/ConnectionTest.php @@ -18,15 +18,11 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\Database; use Doctrine\DBAL\Configuration; -use Doctrine\DBAL\Driver\Mysqli\Driver; -use Doctrine\DBAL\Driver\Mysqli\MysqliConnection; -use Doctrine\DBAL\Driver\ServerInfoAwareConnection; -use Doctrine\DBAL\ForwardCompatibility\Result; +use Doctrine\DBAL\Driver\AbstractMySQLDriver; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySqlPlatform; -use Doctrine\DBAL\VersionAwarePlatformDriver; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Result; use Prophecy\PhpUnit\ProphecyTrait; -use Prophecy\Prophecy\ObjectProphecy; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder; use TYPO3\CMS\Core\Database\Query\QueryBuilder; @@ -69,7 +65,7 @@ class ConnectionTest extends UnitTestCase 'getWrappedConnection', ] ) - ->setConstructorArgs([['platform' => $this->prophesize(MySqlPlatform::class)->reveal()], $this->prophesize(Driver::class)->reveal(), new Configuration(), null]) + ->setConstructorArgs([['platform' => $this->prophesize(MySQLPlatform::class)->reveal()], $this->prophesize(AbstractMySQLDriver::class)->reveal(), new Configuration(), null]) ->getMock(); $this->connection @@ -520,19 +516,9 @@ class ConnectionTest extends UnitTestCase */ public function getServerVersionReportsPlatformVersion(): void { - /** @var MysqliConnection|ObjectProphecy $driverProphet */ - $driverProphet = $this->prophesize(Driver::class); - $driverProphet->willImplement(VersionAwarePlatformDriver::class); - - /** @var MysqliConnection|ObjectProphecy $wrappedConnectionProphet */ - $wrappedConnectionProphet = $this->prophesize(MysqliConnection::class); - $wrappedConnectionProphet->willImplement(ServerInfoAwareConnection::class); - $wrappedConnectionProphet->requiresQueryForServerVersion()->willReturn(false); + $wrappedConnectionProphet = $this->prophesize(Connection::class); $wrappedConnectionProphet->getServerVersion()->willReturn('5.7.11'); - $this->connection - ->method('getDriver') - ->willReturn($driverProphet->reveal()); $this->connection ->method('getWrappedConnection') ->willReturn($wrappedConnectionProphet->reveal()); diff --git a/typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php b/typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php index 42880dd7a3e77777038e1477c5ec943026494d16..a78b0817afc7c738cb8bc7cc0591a9943a67acfb 100644 --- a/typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php +++ b/typo3/sysext/core/Tests/Unit/Database/Platform/PlatformInformationTest.php @@ -18,8 +18,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\Database\Platform; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySqlPlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform; use Prophecy\PhpUnit\ProphecyTrait; @@ -41,7 +41,7 @@ class PlatformInformationTest extends UnitTestCase public function platformDataProvider(): array { return [ - 'mysql' => [$this->prophesize(MySqlPlatform::class)->reveal()], + 'mysql' => [$this->prophesize(MySQLPlatform::class)->reveal()], 'postgresql' => [$this->prophesize(PostgreSqlPlatform::class)->reveal()], 'sqlserver' => [$this->prophesize(SQLServerPlatform::class)->reveal()], 'sqlite' => [$this->prophesize(SqlitePlatform::class)->reveal()], diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php index d0ca4f280e4c51b4bca2b5fbd8f073c3195fb74c..22bd9a39ec36902a04745c98f50dbfdcfe63376e 100644 --- a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php +++ b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php @@ -17,13 +17,13 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\Database\Query; -use Doctrine\DBAL\ForwardCompatibility\Result; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; -use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\PostgreSQL94Platform as PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform as SQLServerPlatform; +use Doctrine\DBAL\Result; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; @@ -1168,13 +1168,13 @@ class QueryBuilderTest extends UnitTestCase { return [ 'mysql' => [ - 'platform' => MySqlPlatform::class, + 'platform' => MySQLPlatform::class, 'quoteChar' => '`', 'input' => '`anIdentifier`', 'expected' => 'anIdentifier', ], 'mysql with spaces' => [ - 'platform' => MySqlPlatform::class, + 'platform' => MySQLPlatform::class, 'quoteChar' => '`', 'input' => ' `anIdentifier` ', 'expected' => 'anIdentifier', @@ -1406,8 +1406,8 @@ class QueryBuilderTest extends UnitTestCase public function castFieldToTextTypeDataProvider(): array { return [ - 'Test cast for MySqlPlatform' => [ - new MySqlPlatform(), + 'Test cast for MySQLPlatform' => [ + new MySQLPlatform(), 'CONVERT(aField, CHAR)', ], 'Test cast for PostgreSqlPlatform' => [ diff --git a/typo3/sysext/core/Tests/Unit/Database/Schema/ConnectionMigratorTest.php b/typo3/sysext/core/Tests/Unit/Database/Schema/ConnectionMigratorTest.php index bc79b7226f2d51b2c214e9f418639019614fae3f..509c33da2c000e2066e6279614f6e423a1e10a99 100644 --- a/typo3/sysext/core/Tests/Unit/Database/Schema/ConnectionMigratorTest.php +++ b/typo3/sysext/core/Tests/Unit/Database/Schema/ConnectionMigratorTest.php @@ -17,7 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\Database\Schema; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\SchemaDiff; use Doctrine\DBAL\Schema\Table; @@ -57,7 +57,7 @@ class ConnectionMigratorTest extends UnitTestCase { parent::setUp(); - $platformMock = $this->prophesize(MySqlPlatform::class); + $platformMock = $this->prophesize(MySQLPlatform::class); $platformMock->quoteIdentifier(Argument::any())->willReturnArgument(0); $this->platform = $platformMock->reveal(); diff --git a/typo3/sysext/core/composer.json b/typo3/sysext/core/composer.json index dff9f48dfa520ce2559327bdecb53cff754fafa7..895dc8c6bb99eab2799c09b55ca34ef5fd42ae60 100644 --- a/typo3/sysext/core/composer.json +++ b/typo3/sysext/core/composer.json @@ -31,7 +31,7 @@ "bacon/bacon-qr-code": "^2.0.4", "christian-riesen/base32": "^1.6", "doctrine/annotations": "^1.11", - "doctrine/dbal": "^2.13.5", + "doctrine/dbal": "^3.2", "doctrine/event-manager": "^1.0.0", "doctrine/lexer": "^1.2.1", "egulias/email-validator": "^3.1", diff --git a/typo3/sysext/indexed_search/Tests/Functional/Utility/LikeWildcardTest.php b/typo3/sysext/indexed_search/Tests/Functional/Utility/LikeWildcardTest.php index 4833fc182fdbbcdbc6685c0faf4a79353d54a946..ac274da1cee173401287fccd38994610ca4bfc7b 100644 --- a/typo3/sysext/indexed_search/Tests/Functional/Utility/LikeWildcardTest.php +++ b/typo3/sysext/indexed_search/Tests/Functional/Utility/LikeWildcardTest.php @@ -17,7 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\IndexedSearch\Tests\Functional\Utility; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\IndexedSearch\Utility\LikeWildcard; @@ -43,7 +43,7 @@ class LikeWildcardTest extends FunctionalTestCase $subject = LikeWildcard::cast($wildcard); // MySQL has support for backslash escape sequences, the expected results needs to take // the additional quoting into account. - if ($connection->getDatabasePlatform() instanceof MySqlPlatform) { + if ($connection->getDatabasePlatform() instanceof MySQLPlatform) { $expected = addcslashes($expected, '\\'); } $expected = $connection->quoteIdentifier($fieldName) . ' ' . $expected; diff --git a/typo3/sysext/install/Classes/Database/PermissionsCheck.php b/typo3/sysext/install/Classes/Database/PermissionsCheck.php index 32f25d3a0673ebf81f63dbc097cc6d0d032fbf6f..5bc7489a3c3715cb294d0dab07d4388ea9f91bd9 100644 --- a/typo3/sysext/install/Classes/Database/PermissionsCheck.php +++ b/typo3/sysext/install/Classes/Database/PermissionsCheck.php @@ -172,7 +172,7 @@ class PermissionsCheck private function checkCreateTable(string $tablename): bool { $connection = $this->getConnection(); - $schema = $connection->getSchemaManager()->createSchema(); + $schema = $connection->createSchemaManager()->createSchema(); $testTable = $schema->createTable($tablename); $testTable->addColumn('id', 'integer', ['unsigned' => true]); $testTable->setPrimaryKey(['id']); @@ -191,8 +191,8 @@ class PermissionsCheck { $connection = $this->getConnection(); try { - $schemaCurrent = $connection->getSchemaManager()->createSchema(); - $schemaNew = $connection->getSchemaManager()->createSchema(); + $schemaCurrent = $connection->createSchemaManager()->createSchema(); + $schemaNew = $connection->createSchemaManager()->createSchema(); $schemaNew->dropTable($tablename); $platform = $connection->getDatabasePlatform(); diff --git a/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php b/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php index 7d1f0aeaf245d8567ecfbf59dc8edd055ca9d64c..262fd279ca02062cf735d903c4c02c2f2f428250 100644 --- a/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php +++ b/typo3/sysext/install/Classes/Service/UpgradeWizardsService.php @@ -17,7 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Install\Service; -use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Schema\Column; use Doctrine\DBAL\Schema\Table; use Symfony\Component\Console\Output\Output; @@ -218,7 +218,7 @@ class UpgradeWizardsService $connection = GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); - $isDefaultConnectionMysql = ($connection->getDatabasePlatform() instanceof MySqlPlatform); + $isDefaultConnectionMysql = ($connection->getDatabasePlatform() instanceof MySQLPlatform); if (!$isDefaultConnectionMysql) { // Not tested on non mysql diff --git a/typo3/sysext/install/Classes/SystemEnvironment/DatabaseCheck.php b/typo3/sysext/install/Classes/SystemEnvironment/DatabaseCheck.php index 4cf20444dcccd34fa74679ed3ce4186501ae8b4c..66ed9043e82a54fc04e0a1fa1ec7678a34237e34 100644 --- a/typo3/sysext/install/Classes/SystemEnvironment/DatabaseCheck.php +++ b/typo3/sysext/install/Classes/SystemEnvironment/DatabaseCheck.php @@ -18,12 +18,10 @@ declare(strict_types=1); namespace TYPO3\CMS\Install\SystemEnvironment; use Doctrine\DBAL\Driver; -use Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver as DoctrineDrizzlePDOMySQLDriver; -use Doctrine\DBAL\Driver\IBMDB2\DB2Driver; +use Doctrine\DBAL\Driver\IBMDB2\Driver as DB2Driver; use Doctrine\DBAL\Driver\Mysqli\Driver as DoctrineMysqliDriver; use Doctrine\DBAL\Driver\OCI8\Driver as DoctrineOCI8Driver; -use Doctrine\DBAL\Driver\PDOOracle\Driver as DoctrinePDOOCIDriver; -use Doctrine\DBAL\Driver\SQLAnywhere\Driver as DoctrineSQLAnywhereDriver; +use Doctrine\DBAL\Driver\PDO\OCI\Driver as DoctrinePDOOCIDriver; use Doctrine\DBAL\Driver\SQLSrv\Driver as DoctrineSQLSrvDriver; use TYPO3\CMS\Core\Database\Driver\PDOMySql\Driver as TYPO3PDOMySqlDriver; use TYPO3\CMS\Core\Database\Driver\PDOPgSql\Driver as TYPO3PDOPgSqlDriver; @@ -107,8 +105,6 @@ class DatabaseCheck implements CheckInterface 'ibm_db2' => DB2Driver::class, 'pdo_sqlsrv' => TYPO3PDOSqlSrvDriver::class, 'mysqli' => DoctrineMysqliDriver::class, - 'drizzle_pdo_mysql' => DoctrineDrizzlePDOMySQLDriver::class, - 'sqlanywhere' => DoctrineSQLAnywhereDriver::class, 'sqlsrv' => DoctrineSQLSrvDriver::class, ]; diff --git a/typo3/sysext/install/composer.json b/typo3/sysext/install/composer.json index 2035f389837f5dbeb5b1f36ea776d8820e3435f1..41c8615c8ce294ec8a10ada10d6cb050c9dcbfea 100644 --- a/typo3/sysext/install/composer.json +++ b/typo3/sysext/install/composer.json @@ -19,7 +19,7 @@ "sort-packages": true }, "require": { - "doctrine/dbal": "^2.13.5", + "doctrine/dbal": "^3.2", "guzzlehttp/promises": "^1.4.0", "nikic/php-parser": "^4.10.4", "symfony/finder": "^5.3.7", diff --git a/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php b/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php index dfa3daa7cc3f13b7c779afd5b4ff800fab61dca4..53b4739084ad0f2af00bad426e7e88c82cbf18bd 100644 --- a/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php +++ b/typo3/sysext/lowlevel/Classes/Database/QueryGenerator.php @@ -1343,7 +1343,7 @@ class QueryGenerator /** * Render table header * - * @param array $row Table columns + * @param array|null $row Table columns * @param array $conf Table TCA * @return string HTML of table header */ @@ -1354,7 +1354,7 @@ class QueryGenerator // Start header row $tableHeader[] = '<thead><tr>'; // Iterate over given columns - foreach ($row as $fieldName => $fieldValue) { + foreach ($row ?? [] as $fieldName => $fieldValue) { if (GeneralUtility::inList($this->settings['queryFields'] ?? '', $fieldName) || !($this->settings['queryFields'] ?? false) && $fieldName !== 'pid' diff --git a/typo3/sysext/redirects/composer.json b/typo3/sysext/redirects/composer.json index df49a468590895d75765600ed5605fdfd39f8bee..26cb267ba3eeaf43093126746c2dd2526479ee7d 100644 --- a/typo3/sysext/redirects/composer.json +++ b/typo3/sysext/redirects/composer.json @@ -19,7 +19,7 @@ "sort-packages": true }, "require": { - "doctrine/dbal": "^2.13.5", + "doctrine/dbal": "^3.2", "psr/http-message": "^1.0", "psr/log": "^1.0", "symfony/console": "^5.3.7",