From 50c0fbc79b4e86d1a500b27d4d519f0ec3b33cae Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 13 Oct 2023 10:59:07 +0200 Subject: [PATCH] Fix DBAL 4 compatibility --- .../Doctrine/Form/DoctrineOrmTypeGuesser.php | 2 +- .../DoctrinePingConnectionMiddleware.php | 16 +- .../Doctrine/Middleware/Debug/Connection.php | 135 ++++---------- .../Middleware/Debug/DBAL3/Connection.php | 174 ++++++++++++++++++ .../Middleware/Debug/DBAL3/Statement.php | 86 +++++++++ .../Doctrine/Middleware/Debug/Driver.php | 16 +- .../Doctrine/Middleware/Debug/Query.php | 16 +- .../Doctrine/Middleware/Debug/Statement.php | 51 ++--- .../PropertyInfo/DoctrineExtractor.php | 10 +- ...octrineDataCollectorWithDebugStackTest.php | 7 + .../Tests/Fixtures/SingleIntIdEntity.php | 5 +- .../Tests/Fixtures/Type/StringWrapperType.php | 8 +- .../Doctrine/Tests/Logger/DbalLoggerTest.php | 8 + .../DoctrinePingConnectionMiddlewareTest.php | 40 ++-- .../Tests/Middleware/Debug/MiddlewareTest.php | 10 +- .../PropertyInfo/Fixtures/DoctrineFooType.php | 8 +- ...rTransportDoctrineSchemaSubscriberTest.php | 8 + .../Doctrine/Tests/Types/UlidTypeTest.php | 23 ++- .../Bridge/Doctrine/Types/AbstractUidType.php | 40 +++- 19 files changed, 466 insertions(+), 197 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php create mode 100644 src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 231b50640f040..6386318ef97d9 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -52,7 +52,7 @@ public function guessType(string $class, string $property) } switch ($metadata->getTypeOfField($property)) { - case Types::ARRAY: + case 'array': // DBAL < 4 case Types::SIMPLE_ARRAY: return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CollectionType', [], Guess::MEDIUM_CONFIDENCE); case Types::BOOLEAN: diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index de925284d09dc..5f8d9496348c8 100644 --- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php +++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Messenger; +use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\Envelope; @@ -33,19 +34,28 @@ protected function handleForManager(EntityManagerInterface $entityManager, Envel return $stack->next()->handle($envelope, $stack); } - private function pingConnection(EntityManagerInterface $entityManager) + private function pingConnection(EntityManagerInterface $entityManager): void { $connection = $entityManager->getConnection(); try { - $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); + $this->executeDummySql($connection); } catch (DBALException $e) { $connection->close(); - $connection->connect(); + // Attempt to reestablish the lazy connection by sending another query. + $this->executeDummySql($connection); } if (!$entityManager->isOpen()) { $this->managerRegistry->resetManager($this->entityManagerName); } } + + /** + * @throws DBALException + */ + private function executeDummySql(Connection $connection): void + { + $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); + } } diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php index e768407bdd137..a0d642dd7d250 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php @@ -14,31 +14,26 @@ use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; use Doctrine\DBAL\Driver\Result; -use Doctrine\DBAL\Driver\Statement as DriverStatement; use Symfony\Component\Stopwatch\Stopwatch; /** * @author Laurent VOULLEMIER + * @author Alexander M. Turek * * @internal */ final class Connection extends AbstractConnectionMiddleware { - private $nestingLevel = 0; - private $debugDataHolder; - private $stopwatch; - private $connectionName; - - public function __construct(ConnectionInterface $connection, DebugDataHolder $debugDataHolder, ?Stopwatch $stopwatch, string $connectionName) - { + public function __construct( + ConnectionInterface $connection, + private DebugDataHolder $debugDataHolder, + private ?Stopwatch $stopwatch, + private string $connectionName, + ) { parent::__construct($connection); - - $this->debugDataHolder = $debugDataHolder; - $this->stopwatch = $stopwatch; - $this->connectionName = $connectionName; } - public function prepare(string $sql): DriverStatement + public function prepare(string $sql): Statement { return new Statement( parent::prepare($sql), @@ -53,135 +48,79 @@ public function query(string $sql): Result { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } - + $this->stopwatch?->start('doctrine', 'doctrine'); $query->start(); try { - $result = parent::query($sql); + return parent::query($sql); } finally { $query->stop(); - - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $this->stopwatch?->stop('doctrine'); } - - return $result; } public function exec(string $sql): int { $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } - + $this->stopwatch?->start('doctrine', 'doctrine'); $query->start(); try { $affectedRows = parent::exec($sql); } finally { $query->stop(); - - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $this->stopwatch?->stop('doctrine'); } return $affectedRows; } - public function beginTransaction(): bool + public function beginTransaction(): void { - $query = null; - if (1 === ++$this->nestingLevel) { - $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); - } - - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } + $query = new Query('"START TRANSACTION"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); - if (null !== $query) { - $query->start(); - } + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); try { - $ret = parent::beginTransaction(); + parent::beginTransaction(); } finally { - if (null !== $query) { - $query->stop(); - } - - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $query->stop(); + $this->stopwatch?->stop('doctrine'); } - - return $ret; } - public function commit(): bool + public function commit(): void { - $query = null; - if (1 === $this->nestingLevel--) { - $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); - } + $query = new Query('"COMMIT"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } - - if (null !== $query) { - $query->start(); - } + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); try { - $ret = parent::commit(); + parent::commit(); } finally { - if (null !== $query) { - $query->stop(); - } - - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $query->stop(); + $this->stopwatch?->stop('doctrine'); } - - return $ret; } - public function rollBack(): bool + public function rollBack(): void { - $query = null; - if (1 === $this->nestingLevel--) { - $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); - } - - if (null !== $this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } + $query = new Query('"ROLLBACK"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); - if (null !== $query) { - $query->start(); - } + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); try { - $ret = parent::rollBack(); + parent::rollBack(); } finally { - if (null !== $query) { - $query->stop(); - } - - if (null !== $this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $query->stop(); + $this->stopwatch?->stop('doctrine'); } - - return $ret; } } diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php new file mode 100644 index 0000000000000..1bcb6c22e0c3d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug\DBAL3; + +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; +use Doctrine\DBAL\Driver\Middleware\AbstractConnectionMiddleware; +use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Connection extends AbstractConnectionMiddleware +{ + /** @var int */ + private $nestingLevel = 0; + private $debugDataHolder; + private $stopwatch; + private $connectionName; + + public function __construct( + ConnectionInterface $connection, + DebugDataHolder $debugDataHolder, + ?Stopwatch $stopwatch, + string $connectionName + ) { + $this->connectionName = $connectionName; + $this->stopwatch = $stopwatch; + $this->debugDataHolder = $debugDataHolder; + + parent::__construct($connection); + } + + public function prepare(string $sql): StatementInterface + { + return new Statement( + parent::prepare($sql), + $this->debugDataHolder, + $this->connectionName, + $sql, + $this->stopwatch, + ); + } + + public function query(string $sql): Result + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + if ($this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + $query->start(); + + try { + return parent::query($sql); + } finally { + $query->stop(); + if ($this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + if ($this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + $query->start(); + + try { + return parent::exec($sql); + } finally { + $query->stop(); + if ($this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + } + + public function beginTransaction(): bool + { + $query = null; + if (1 === ++$this->nestingLevel) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); + } + + if ($this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + if ($query) { + $query->start(); + } + + try { + return parent::beginTransaction(); + } finally { + if ($query) { + $query->stop(); + } + if ($this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + } + + public function commit(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); + } + + if ($this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + if ($query) { + $query->start(); + } + + try { + return parent::commit(); + } finally { + if ($query) { + $query->stop(); + } + if ($this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + } + + public function rollBack(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); + } + + if ($this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + if ($query) { + $query->start(); + } + + try { + return parent::rollBack(); + } finally { + if ($query) { + $query->stop(); + } + if ($this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php new file mode 100644 index 0000000000000..16217c2f46a51 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Middleware\Debug\DBAL3; + +use Doctrine\DBAL\Driver\Middleware\AbstractStatementMiddleware; +use Doctrine\DBAL\Driver\Result as ResultInterface; +use Doctrine\DBAL\Driver\Statement as StatementInterface; +use Doctrine\DBAL\ParameterType; +use Symfony\Bridge\Doctrine\Middleware\Debug\DebugDataHolder; +use Symfony\Bridge\Doctrine\Middleware\Debug\Query; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Laurent VOULLEMIER + * + * @internal + */ +final class Statement extends AbstractStatementMiddleware +{ + private $query; + private $debugDataHolder; + private $connectionName; + private $stopwatch; + + public function __construct( + StatementInterface $statement, + DebugDataHolder $debugDataHolder, + string $connectionName, + string $sql, + Stopwatch $stopwatch = null + ) { + $this->stopwatch = $stopwatch; + $this->connectionName = $connectionName; + $this->debugDataHolder = $debugDataHolder; + $this->query = new Query($sql); + + parent::__construct($statement); + } + + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool + { + $this->query->setParam($param, $variable, $type); + + return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); + } + + public function bindValue($param, $value, $type = ParameterType::STRING): bool + { + $this->query->setValue($param, $value, $type); + + return parent::bindValue($param, $value, $type); + } + + public function execute($params = null): ResultInterface + { + if (null !== $params) { + $this->query->setValues($params); + } + + // clone to prevent variables by reference to change + $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); + + if ($this->stopwatch) { + $this->stopwatch->start('doctrine', 'doctrine'); + } + $query->start(); + + try { + return parent::execute($params); + } finally { + $query->stop(); + if ($this->stopwatch) { + $this->stopwatch->stop('doctrine'); + } + } + } +} diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php index 7f7fdd3bf0d8d..090b1643f6ad6 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Driver.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Middleware\Debug; use Doctrine\DBAL\Driver as DriverInterface; +use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Symfony\Component\Stopwatch\Stopwatch; @@ -35,10 +36,21 @@ public function __construct(DriverInterface $driver, DebugDataHolder $debugDataH $this->connectionName = $connectionName; } - public function connect(array $params): Connection + public function connect(array $params): ConnectionInterface { + $connection = parent::connect($params); + + if ('void' !== (string) (new \ReflectionMethod(DriverInterface\Connection::class, 'commit'))->getReturnType()) { + return new DBAL3\Connection( + $connection, + $this->debugDataHolder, + $this->stopwatch, + $this->connectionName + ); + } + return new Connection( - parent::connect($params), + $connection, $this->debugDataHolder, $this->stopwatch, $this->connectionName diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php index eb835caa41b25..6ab402e1b7779 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -46,10 +46,11 @@ public function stop(): void } /** - * @param string|int $param - * @param mixed $variable + * @param string|int $param + * @param mixed $variable + * @param int|ParameterType $type */ - public function setParam($param, &$variable, int $type): void + public function setParam($param, &$variable, $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; @@ -59,10 +60,11 @@ public function setParam($param, &$variable, int $type): void } /** - * @param string|int $param - * @param mixed $value + * @param string|int $param + * @param mixed $value + * @param int|ParameterType $type */ - public function setValue($param, $value, int $type): void + public function setValue($param, $value, $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; @@ -95,7 +97,7 @@ public function getParams(): array } /** - * @return array + * @return array */ public function getTypes(): array { diff --git a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php index a40cdaa9695ba..3f4ba10fc2138 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php @@ -19,65 +19,46 @@ /** * @author Laurent VOULLEMIER + * @author Alexander M. Turek * * @internal */ final class Statement extends AbstractStatementMiddleware { - private $debugDataHolder; - private $connectionName; - private $query; - private $stopwatch; - - public function __construct(StatementInterface $statement, DebugDataHolder $debugDataHolder, string $connectionName, string $sql, Stopwatch $stopwatch = null) - { + private Query $query; + + public function __construct( + StatementInterface $statement, + private DebugDataHolder $debugDataHolder, + private string $connectionName, + string $sql, + private ?Stopwatch $stopwatch = null, + ) { parent::__construct($statement); - $this->debugDataHolder = $debugDataHolder; - $this->connectionName = $connectionName; $this->query = new Query($sql); - $this->stopwatch = $stopwatch; - } - - public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool - { - $this->query->setParam($param, $variable, $type); - - return parent::bindParam($param, $variable, $type, ...\array_slice(\func_get_args(), 3)); } - public function bindValue($param, $value, $type = ParameterType::STRING): bool + public function bindValue(int|string $param, mixed $value, ParameterType $type): void { $this->query->setValue($param, $value, $type); - return parent::bindValue($param, $value, $type); + parent::bindValue($param, $value, $type); } - public function execute($params = null): ResultInterface + public function execute(): ResultInterface { - if (null !== $params) { - $this->query->setValues($params); - } - // clone to prevent variables by reference to change $this->debugDataHolder->addQuery($this->connectionName, $query = clone $this->query); - if ($this->stopwatch) { - $this->stopwatch->start('doctrine', 'doctrine'); - } - + $this->stopwatch?->start('doctrine', 'doctrine'); $query->start(); try { - $result = parent::execute($params); + return parent::execute(); } finally { $query->stop(); - - if ($this->stopwatch) { - $this->stopwatch->stop('doctrine'); - } + $this->stopwatch?->stop('doctrine'); } - - return $result; } } diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index ccd53c1ebe7c6..f33a62cb6257c 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -167,8 +167,8 @@ public function getTypes(string $class, string $property, array $context = []) break; case Type::BUILTIN_TYPE_ARRAY: switch ($typeOfField) { - case Types::ARRAY: - case 'json_array': + case 'array': // DBAL < 4 + case 'json_array': // DBAL < 3 // return null if $enumType is set, because we can't determine if collectionKeyType is string or int if ($enumType) { return null; @@ -281,7 +281,7 @@ private function getPhpType(string $doctrineType): ?string case Types::BINARY: return Type::BUILTIN_TYPE_RESOURCE; - case Types::OBJECT: + case 'object': // DBAL < 4 case Types::DATE_MUTABLE: case Types::DATETIME_MUTABLE: case Types::DATETIMETZ_MUTABLE: @@ -294,9 +294,9 @@ private function getPhpType(string $doctrineType): ?string case Types::DATEINTERVAL: return Type::BUILTIN_TYPE_OBJECT; - case Types::ARRAY: + case 'array': // DBAL < 4 case Types::SIMPLE_ARRAY: - case 'json_array': + case 'json_array': // DBAL < 3 return Type::BUILTIN_TYPE_ARRAY; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php index 64bee1203b781..690c5aa6f9b8d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php @@ -32,6 +32,13 @@ class DoctrineDataCollectorWithDebugStackTest extends TestCase { use DoctrineDataCollectorTestTrait; + public static function setUpBeforeClass(): void + { + if (!class_exists(DebugStack::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + } + public function testReset() { $queries = [ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php index 94b47da855a37..85c1c0cc20ea6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; @@ -27,8 +28,8 @@ class SingleIntIdEntity #[Column(type: 'string', nullable: true)] public $name; - /** @Column(type="array", nullable=true) */ - #[Column(type: 'array', nullable: true)] + /** @Column(type="json", nullable=true) */ + #[Column(type: Types::JSON, nullable: true)] public $phoneNumbers = []; public function __construct($id, $name) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php index d01148f3b018c..33481663b6152 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Type/StringWrapperType.php @@ -18,20 +18,16 @@ class StringWrapperType extends StringType { /** * {@inheritdoc} - * - * @return mixed */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { return $value instanceof StringWrapper ? $value->getString() : null; } /** * {@inheritdoc} - * - * @return mixed */ - public function convertToPHPValue($value, AbstractPlatform $platform) + public function convertToPHPValue($value, AbstractPlatform $platform): StringWrapper { return new StringWrapper($value); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php index 2e9ed80e3115a..b43bb93d7dd52 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Logger; +use Doctrine\DBAL\Logging\SQLLogger; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Bridge\Doctrine\Logger\DbalLogger; @@ -20,6 +21,13 @@ */ class DbalLoggerTest extends TestCase { + public static function setUpBeforeClass(): void + { + if (!class_exists(SQLLogger::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + } + /** * @dataProvider getLogFixtures */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php index 6c7bf67bc08af..a478f72266ffb 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php @@ -13,8 +13,11 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Result; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; @@ -47,16 +50,24 @@ protected function setUp(): void public function testMiddlewarePingOk() { - $this->connection->expects($this->once()) - ->method('getDatabasePlatform') - ->will($this->throwException(new DBALException())); + $this->connection->method('getDatabasePlatform') + ->willReturn($this->mockPlatform()); + + $this->connection->expects($this->exactly(2)) + ->method('executeQuery') + ->willReturnCallback(function () { + static $counter = 0; + + if (1 === ++$counter) { + throw $this->createMock(DBALException::class); + } + + return $this->createMock(Result::class); + }); $this->connection->expects($this->once()) ->method('close') ; - $this->connection->expects($this->once()) - ->method('connect') - ; $envelope = new Envelope(new \stdClass(), [ new ConsumedByWorkerStamp(), @@ -66,9 +77,8 @@ public function testMiddlewarePingOk() public function testMiddlewarePingResetEntityManager() { - $this->connection->expects($this->once()) - ->method('getDatabasePlatform') - ->will($this->throwException(new DBALException())); + $this->connection->method('getDatabasePlatform') + ->willReturn($this->mockPlatform()); $this->entityManager->expects($this->once()) ->method('isOpen') @@ -112,11 +122,17 @@ public function testMiddlewareNoPingInNonWorkerContext() $this->connection->expects($this->never()) ->method('close') ; - $this->connection->expects($this->never()) - ->method('connect') - ; $envelope = new Envelope(new \stdClass()); $this->middleware->handle($envelope, $this->getStackMock()); } + + /** @return AbstractPlatform&MockObject */ + private function mockPlatform(): AbstractPlatform + { + $platform = $this->createMock(AbstractPlatform::class); + $platform->method('getDummySelectSQL')->willReturn('SELECT 1'); + + return $platform; + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php index 4e546b20890c6..e59428783e4dd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -178,13 +178,13 @@ public static function provideEndTransactionMethod(): array { return [ 'commit' => [ - static function (Connection $conn): bool { + static function (Connection $conn): ?bool { return $conn->commit(); }, '"COMMIT"', ], 'rollback' => [ - static function (Connection $conn): bool { + static function (Connection $conn): ?bool { return $conn->rollBack(); }, '"ROLLBACK"', @@ -236,7 +236,7 @@ public static function provideExecuteAndEndTransactionMethods(): array static function (Connection $conn, string $sql) { return $conn->executeStatement($sql); }, - static function (Connection $conn): bool { + static function (Connection $conn): ?bool { return $conn->commit(); }, ], @@ -244,7 +244,7 @@ static function (Connection $conn): bool { static function (Connection $conn, string $sql): Result { return $conn->executeQuery($sql); }, - static function (Connection $conn): bool { + static function (Connection $conn): ?bool { return $conn->rollBack(); }, ], @@ -252,7 +252,7 @@ static function (Connection $conn): bool { static function (Connection $conn, string $sql): Result { return $conn->prepare($sql)->executeQuery(); }, - static function (Connection $conn): bool { + static function (Connection $conn): ?bool { return $conn->commit(); }, ], diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php index 7c09108fde562..cc2e8154a7c41 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php @@ -43,10 +43,8 @@ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $pla /** * {@inheritdoc} - * - * @return mixed */ - public function convertToDatabaseValue($value, AbstractPlatform $platform) + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { if (null === $value) { return null; @@ -60,10 +58,8 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform) /** * {@inheritdoc} - * - * @return mixed */ - public function convertToPHPValue($value, AbstractPlatform $platform) + public function convertToPHPValue($value, AbstractPlatform $platform): ?Foo { if (null === $value) { return null; diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php index ff4ab2c27a19c..f846a4f38225a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaSubscriberTest.php @@ -49,6 +49,10 @@ public function testPostGenerateSchema() public function testOnSchemaCreateTable() { + if (!class_exists(SchemaCreateTableEventArgs::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + $platform = $this->createMock(AbstractPlatform::class); $table = new Table('queue_table'); $event = new SchemaCreateTableEventArgs($table, [], [], $platform); @@ -80,6 +84,10 @@ public function testOnSchemaCreateTable() public function testOnSchemaCreateTableNoExtraSql() { + if (!class_exists(SchemaCreateTableEventArgs::class)) { + self::markTestSkipped('This test requires DBAL < 4.'); + } + $platform = $this->createMock(AbstractPlatform::class); $table = new Table('queue_table'); $event = new SchemaCreateTableEventArgs($table, [], [], $platform); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php index c1db2bbe70124..6f78bf3ecce92 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php @@ -15,7 +15,7 @@ use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; use PHPUnit\Framework\TestCase; @@ -26,6 +26,9 @@ // DBAL 2 compatibility class_exists('Doctrine\DBAL\Platforms\PostgreSqlPlatform'); +// DBAL 3 compatibility +class_exists('Doctrine\DBAL\Platforms\SqlitePlatform'); + final class UlidTypeTest extends TestCase { private const DUMMY_ULID = '01EEDQEK6ZAZE93J8KG5B4MBJC'; @@ -85,25 +88,25 @@ public function testNotSupportedTypeConversionForDatabaseValue() { $this->expectException(ConversionException::class); - $this->type->convertToDatabaseValue(new \stdClass(), new SqlitePlatform()); + $this->type->convertToDatabaseValue(new \stdClass(), new SQLitePlatform()); } public function testNullConversionForDatabaseValue() { - $this->assertNull($this->type->convertToDatabaseValue(null, new SqlitePlatform())); + $this->assertNull($this->type->convertToDatabaseValue(null, new SQLitePlatform())); } public function testUlidInterfaceConvertsToPHPValue() { $ulid = $this->createMock(AbstractUid::class); - $actual = $this->type->convertToPHPValue($ulid, new SqlitePlatform()); + $actual = $this->type->convertToPHPValue($ulid, new SQLitePlatform()); $this->assertSame($ulid, $actual); } public function testUlidConvertsToPHPValue() { - $ulid = $this->type->convertToPHPValue(self::DUMMY_ULID, new SqlitePlatform()); + $ulid = $this->type->convertToPHPValue(self::DUMMY_ULID, new SQLitePlatform()); $this->assertInstanceOf(Ulid::class, $ulid); $this->assertEquals(self::DUMMY_ULID, $ulid->__toString()); @@ -113,19 +116,19 @@ public function testInvalidUlidConversionForPHPValue() { $this->expectException(ConversionException::class); - $this->type->convertToPHPValue('abcdefg', new SqlitePlatform()); + $this->type->convertToPHPValue('abcdefg', new SQLitePlatform()); } public function testNullConversionForPHPValue() { - $this->assertNull($this->type->convertToPHPValue(null, new SqlitePlatform())); + $this->assertNull($this->type->convertToPHPValue(null, new SQLitePlatform())); } public function testReturnValueIfUlidForPHPValue() { $ulid = new Ulid(); - $this->assertSame($ulid, $this->type->convertToPHPValue($ulid, new SqlitePlatform())); + $this->assertSame($ulid, $this->type->convertToPHPValue($ulid, new SQLitePlatform())); } public function testGetName() @@ -144,7 +147,7 @@ public function testGetGuidTypeDeclarationSQL(AbstractPlatform $platform, string public static function provideSqlDeclarations(): \Generator { yield [new PostgreSQLPlatform(), 'UUID']; - yield [new SqlitePlatform(), 'BLOB']; + yield [new SQLitePlatform(), 'BLOB']; yield [new MySQLPlatform(), 'BINARY(16)']; if (class_exists(MariaDBPlatform::class)) { @@ -154,6 +157,6 @@ public static function provideSqlDeclarations(): \Generator public function testRequiresSQLCommentHint() { - $this->assertTrue($this->type->requiresSQLCommentHint(new SqlitePlatform())); + $this->assertTrue($this->type->requiresSQLCommentHint(new SQLitePlatform())); } } diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php index 003093aec8845..fa1a72fa4eb33 100644 --- a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php +++ b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php @@ -13,6 +13,8 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidType; +use Doctrine\DBAL\Types\Exception\ValueNotConvertible; use Doctrine\DBAL\Types\Type; use Symfony\Component\Uid\AbstractUid; @@ -33,7 +35,7 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st } return $platform->getBinaryTypeDeclarationSQL([ - 'length' => '16', + 'length' => 16, 'fixed' => true, ]); } @@ -50,13 +52,13 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?Abstract } if (!\is_string($value)) { - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + $this->throwInvalidType($value); } try { return $this->getUidClass()::fromString($value); } catch (\InvalidArgumentException $e) { - throw ConversionException::conversionFailed($value, $this->getName(), $e); + $this->throwValueNotConvertible($value, $e); } } @@ -78,13 +80,13 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str } if (!\is_string($value)) { - throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + $this->throwInvalidType($value); } try { return $this->getUidClass()::fromString($value)->$toString(); } catch (\InvalidArgumentException $e) { - throw ConversionException::conversionFailed($value, $this->getName()); + $this->throwValueNotConvertible($value, $e); } } @@ -105,4 +107,32 @@ private function hasNativeGuidType(AbstractPlatform $platform): bool return $platform->getGuidTypeDeclarationSQL([]) !== $platform->$method(['fixed' => true, 'length' => 36]); } + + /** + * @param mixed $value + * + * @return never + */ + private function throwInvalidType($value): void + { + if (!class_exists(InvalidType::class)) { + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'string', AbstractUid::class]); + } + + throw InvalidType::new($value, $this->getName(), ['null', 'string', AbstractUid::class]); + } + + /** + * @param mixed $value + * + * @return never + */ + private function throwValueNotConvertible($value, \Throwable $previous): void + { + if (!class_exists(ValueNotConvertible::class)) { + throw ConversionException::conversionFailed($value, $this->getName(), $previous); + } + + throw ValueNotConvertible::new($value, $this->getName(), null, $previous); + } }