diff --git a/.github/patch-types.php b/.github/patch-types.php index 2517e1dab835d..b4bb5d1943695 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -23,6 +23,8 @@ } // no break; case false !== strpos($file, '/vendor/'): + case false !== strpos($file, '/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php'): + case false !== strpos($file, '/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3'): case false !== strpos($file, '/src/Symfony/Bridge/PhpUnit/'): case false !== strpos($file, '/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php'): case false !== strpos($file, '/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php'): diff --git a/composer.json b/composer.json index 9a972bfdddc58..858be8f966dce 100644 --- a/composer.json +++ b/composer.json @@ -131,8 +131,8 @@ "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.12", + "doctrine/dbal": "^2.13.1|^3", + "doctrine/orm": "^2.12|^3", "dragonmantank/cron-expression": "^3.1", "egulias/email-validator": "^2.1.10|^3.1|^4", "guzzlehttp/promises": "^1.4", diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 1847e50eeef28..f2c2cc0c6eb0f 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -60,7 +60,7 @@ public function guessType(string $class, string $property): ?TypeGuess } return match ($metadata->getTypeOfField($property)) { - Types::ARRAY, + 'array', Types::SIMPLE_ARRAY => new TypeGuess(CollectionType::class, [], Guess::MEDIUM_CONFIDENCE), Types::BOOLEAN => new TypeGuess(CheckboxType::class, [], Guess::HIGH_CONFIDENCE), Types::DATETIME_MUTABLE, diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php index 1831715039d2e..08e1269b61e9c 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; @@ -38,14 +39,23 @@ private function pingConnection(EntityManagerInterface $entityManager): void $connection = $entityManager->getConnection(); try { - $connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL()); + $this->executeDummySql($connection); } catch (DBALException) { $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 f55a0d220a874..a0d642dd7d250 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Connection.php @@ -14,18 +14,16 @@ 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 int $nestingLevel = 0; - public function __construct( ConnectionInterface $connection, private DebugDataHolder $debugDataHolder, @@ -35,7 +33,7 @@ public function __construct( parent::__construct($connection); } - public function prepare(string $sql): DriverStatement + public function prepare(string $sql): Statement { return new Statement( parent::prepare($sql), @@ -54,13 +52,11 @@ public function query(string $sql): Result $query->start(); try { - $result = parent::query($sql); + return parent::query($sql); } finally { $query->stop(); $this->stopwatch?->stop('doctrine'); } - - return $result; } public function exec(string $sql): int @@ -80,63 +76,51 @@ public function exec(string $sql): int 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"')); - } + $query = new Query('"START TRANSACTION"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); $this->stopwatch?->start('doctrine', 'doctrine'); - $query?->start(); + $query->start(); try { - $ret = parent::beginTransaction(); + parent::beginTransaction(); } finally { - $query?->stop(); + $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); $this->stopwatch?->start('doctrine', 'doctrine'); - $query?->start(); + $query->start(); try { - $ret = parent::commit(); + parent::commit(); } finally { - $query?->stop(); + $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"')); - } + $query = new Query('"ROLLBACK"'); + $this->debugDataHolder->addQuery($this->connectionName, $query); $this->stopwatch?->start('doctrine', 'doctrine'); - $query?->start(); + $query->start(); try { - $ret = parent::rollBack(); + parent::rollBack(); } finally { - $query?->stop(); + $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..e3bec4d611780 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Connection.php @@ -0,0 +1,133 @@ + + * + * 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 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 +{ + private int $nestingLevel = 0; + + public function __construct( + ConnectionInterface $connection, + private DebugDataHolder $debugDataHolder, + private ?Stopwatch $stopwatch, + private string $connectionName, + ) { + parent::__construct($connection); + } + + public function prepare(string $sql): Statement + { + 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)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::query($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function exec(string $sql): int + { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query($sql)); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::exec($sql); + } finally { + $query->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function beginTransaction(): bool + { + $query = null; + if (1 === ++$this->nestingLevel) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"START TRANSACTION"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::beginTransaction(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function commit(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"COMMIT"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::commit(); + } finally { + $query?->stop(); + $this->stopwatch?->stop('doctrine'); + } + } + + public function rollBack(): bool + { + $query = null; + if (1 === $this->nestingLevel--) { + $this->debugDataHolder->addQuery($this->connectionName, $query = new Query('"ROLLBACK"')); + } + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query?->start(); + + try { + return parent::rollBack(); + } finally { + $query?->stop(); + $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..6da904f3271ec --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/DBAL3/Statement.php @@ -0,0 +1,76 @@ + + * + * 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 $query; + + public function __construct( + StatementInterface $statement, + private DebugDataHolder $debugDataHolder, + private string $connectionName, + string $sql, + private ?Stopwatch $stopwatch = null, + ) { + parent::__construct($statement); + + $this->query = new Query($sql); + } + + 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); + + $this->stopwatch?->start('doctrine', 'doctrine'); + $query->start(); + + try { + return parent::execute($params); + } finally { + $query->stop(); + $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 c66bed90d08fd..c5d6ee5914598 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; @@ -24,17 +25,28 @@ final class Driver extends AbstractDriverMiddleware { public function __construct( DriverInterface $driver, - private DebugDataHolder $debugDataHolder, - private ?Stopwatch $stopwatch, - private string $connectionName, + private readonly DebugDataHolder $debugDataHolder, + private readonly ?Stopwatch $stopwatch, + private readonly string $connectionName, ) { parent::__construct($driver); } - 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 695a0a1535aa2..b2668ed1dc366 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Query.php @@ -21,13 +21,15 @@ class Query { private array $params = []; + + /** @var array */ private array $types = []; private ?float $start = null; private ?float $duration = null; public function __construct( - private string $sql, + private readonly string $sql, ) { } @@ -52,7 +54,7 @@ public function setParam(string|int $param, mixed &$variable, int $type): void $this->types[$idx] = $type; } - public function setValue(string|int $param, mixed $value, int $type): void + public function setValue(string|int $param, mixed $value, ParameterType|int $type): void { // Numeric indexes start at 0 in profiler $idx = \is_int($param) ? $param - 1 : $param; @@ -85,7 +87,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 67cf2f5e3119e..3f4ba10fc2138 100644 --- a/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php +++ b/src/Symfony/Bridge/Doctrine/Middleware/Debug/Statement.php @@ -19,6 +19,7 @@ /** * @author Laurent VOULLEMIER + * @author Alexander M. Turek * * @internal */ @@ -38,26 +39,15 @@ public function __construct( $this->query = new Query($sql); } - 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); @@ -65,12 +55,10 @@ public function execute($params = null): ResultInterface $query->start(); try { - $result = parent::execute($params); + return parent::execute(); } finally { $query->stop(); $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 c567fc37fd835..8bf6c7c868264 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -14,9 +14,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; -use Doctrine\ORM\Mapping\Embedded; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; @@ -46,7 +45,7 @@ public function getProperties(string $class, array $context = []): ?array $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); - if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && $metadata->embeddedClasses) { + if ($metadata instanceof ClassMetadata && $metadata->embeddedClasses) { $properties = array_filter($properties, fn ($property) => !str_contains($property, '.')); $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); @@ -65,7 +64,7 @@ public function getTypes(string $class, string $property, array $context = []): $class = $metadata->getAssociationTargetClass($property); if ($metadata->isSingleValuedAssociation($property)) { - if ($metadata instanceof ClassMetadataInfo) { + if ($metadata instanceof ClassMetadata) { $associationMapping = $metadata->getAssociationMapping($property); $nullable = $this->isAssociationNullable($associationMapping); @@ -78,11 +77,10 @@ public function getTypes(string $class, string $property, array $context = []): $collectionKeyType = Type::BUILTIN_TYPE_INT; - if ($metadata instanceof ClassMetadataInfo) { + if ($metadata instanceof ClassMetadata) { $associationMapping = $metadata->getAssociationMapping($property); if (isset($associationMapping['indexBy'])) { - /** @var ClassMetadataInfo $subMetadata */ $subMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']); // Check if indexBy value is a property @@ -94,7 +92,6 @@ public function getTypes(string $class, string $property, array $context = []): // Maybe the column name is the association join column? $associationMapping = $subMetadata->getAssociationMapping($fieldName); - /** @var ClassMetadataInfo $subMetadata */ $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($fieldName); $subMetadata = $this->entityManager->getClassMetadata($associationMapping['targetEntity']); @@ -122,7 +119,7 @@ public function getTypes(string $class, string $property, array $context = []): )]; } - if ($metadata instanceof ClassMetadataInfo && class_exists(Embedded::class) && isset($metadata->embeddedClasses[$property])) { + if ($metadata instanceof ClassMetadata && isset($metadata->embeddedClasses[$property])) { return [new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])]; } @@ -133,7 +130,7 @@ public function getTypes(string $class, string $property, array $context = []): return null; } - $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); + $nullable = $metadata instanceof ClassMetadata && $metadata->isNullable($property); $enumType = null; if (null !== $enumClass = $metadata->getFieldMapping($property)['enumType'] ?? null) { $enumType = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $enumClass); @@ -162,7 +159,7 @@ public function getTypes(string $class, string $property, array $context = []): break; case Type::BUILTIN_TYPE_ARRAY: switch ($typeOfField) { - case Types::ARRAY: + case 'array': case 'json_array': // return null if $enumType is set, because we can't determine if collectionKeyType is string or int if ($enumType) { @@ -219,9 +216,11 @@ private function getMetadata(string $class): ?ClassMetadata /** * Determines whether an association is nullable. * + * @param array|AssociationMapping $associationMapping + * * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 */ - private function isAssociationNullable(array $associationMapping): bool + private function isAssociationNullable(array|AssociationMapping $associationMapping): bool { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; @@ -258,7 +257,7 @@ private function getPhpType(string $doctrineType): ?string Types::BOOLEAN => Type::BUILTIN_TYPE_BOOL, Types::BLOB, Types::BINARY => Type::BUILTIN_TYPE_RESOURCE, - Types::OBJECT, + 'object', Types::DATE_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, @@ -269,7 +268,7 @@ private function getPhpType(string $doctrineType): ?string Types::DATETIMETZ_IMMUTABLE, Types::TIME_IMMUTABLE, Types::DATEINTERVAL => Type::BUILTIN_TYPE_OBJECT, - Types::ARRAY, + 'array', Types::SIMPLE_ARRAY, 'json_array' => Type::BUILTIN_TYPE_ARRAY, default => null, diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorWithDebugStackTest.php index 64bee1203b781..8c1b5ab592871 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/LegacyQueryMock.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/LegacyQueryMock.php new file mode 100644 index 0000000000000..5ec46f606a8d9 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/LegacyQueryMock.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\DBAL\Result; +use Doctrine\ORM\AbstractQuery; + +class LegacyQueryMock extends AbstractQuery +{ + public function __construct() + { + } + + /** + * @return array|string + */ + public function getSQL() + { + } + + /** + * @return Result|int + */ + protected function _doExecute() + { + } +} 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/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index c1cef742e3d28..67f600f5d145e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -13,13 +13,17 @@ use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\GuidType; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\Fixtures\EmbeddedIdentifierEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\LegacyQueryMock; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Bridge\Doctrine\Types\UlidType; @@ -46,13 +50,11 @@ public function testIdentifierTypeIsIntegerArray() $this->checkIdentifierType(SingleIntIdEntity::class, class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY); } - protected function checkIdentifierType($classname, $expectedType) + protected function checkIdentifierType(string $classname, $expectedType) { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -63,7 +65,7 @@ protected function checkIdentifierType($classname, $expectedType) ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [1, 2], $expectedType) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -83,9 +85,7 @@ public function testFilterNonIntegerValues() { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -96,7 +96,7 @@ public function testFilterNonIntegerValues() ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [1, 2, 3, '9223372036854775808'], class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -119,9 +119,7 @@ public function testFilterEmptyUuids($entityClass) { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -132,7 +130,7 @@ public function testFilterEmptyUuids($entityClass) ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', ['71c5fd46-3f16-4abb-bad7-90ac1e654a2d', 'b98e8e11-2897-44df-ad24-d2627eb7f499'], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -164,9 +162,7 @@ public function testFilterUid($entityClass) $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -177,7 +173,7 @@ public function testFilterUid($entityClass) ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [Uuid::fromString('71c5fd46-3f16-4abb-bad7-90ac1e654a2d')->toBinary(), Uuid::fromString('b98e8e11-2897-44df-ad24-d2627eb7f499')->toBinary()], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -209,7 +205,7 @@ public function testUidThrowProperException($entityClass) $em = DoctrineTestHelper::createTestEntityManager(); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -232,9 +228,7 @@ public function testEmbeddedIdentifierName() { $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -245,7 +239,7 @@ public function testEmbeddedIdentifierName() ->with('ORMQueryBuilderLoader_getEntitiesByIds_id_value', [1, 2, 3], class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY) ->willReturn($query); - $qb = $this->getMockBuilder(\Doctrine\ORM\QueryBuilder::class) + $qb = $this->getMockBuilder(QueryBuilder::class) ->setConstructorArgs([$em]) ->onlyMethods(['getQuery']) ->getMock(); @@ -254,13 +248,13 @@ public function testEmbeddedIdentifierName() ->willReturn($query); $qb->select('e') - ->from('Symfony\Bridge\Doctrine\Tests\Fixtures\EmbeddedIdentifierEntity', 'e'); + ->from(EmbeddedIdentifierEntity::class, 'e'); $loader = new ORMQueryBuilderLoader($qb); $loader->getEntitiesByIds('id.value', [1, '', 2, 3, 'foo']); } - public static function provideGuidEntityClasses() + public static function provideGuidEntityClasses(): array { return [ ['Symfony\Bridge\Doctrine\Tests\Fixtures\GuidIdEntity'], @@ -268,26 +262,21 @@ public static function provideGuidEntityClasses() ]; } - public static function provideUidEntityClasses() + public static function provideUidEntityClasses(): array { return [ ['Symfony\Bridge\Doctrine\Tests\Fixtures\UuidIdEntity'], ['Symfony\Bridge\Doctrine\Tests\Fixtures\UlidIdEntity'], ]; } -} - -class QueryMock extends AbstractQuery -{ - public function __construct() - { - } - public function getSQL(): array|string + /** + * @return (LegacyQueryMock&MockObject)|(Query&MockObject) + */ + private function getQueryMock(): AbstractQuery { - } + $class = ((new \ReflectionClass(Query::class))->isFinal()) ? LegacyQueryMock::class : Query::class; - protected function _doExecute(): Result|int - { + return $this->createMock($class); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php index f211f291f873a..930ee9994879e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php @@ -13,6 +13,8 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\JoinColumnMapping; +use Doctrine\ORM\Mapping\ManyToOneAssociationMapping; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; use PHPUnit\Framework\TestCase; @@ -69,33 +71,49 @@ public function testRequiredGuesserSimpleFieldNullable() public function testRequiredGuesserOneToOneNullable() { - $classMetadata = $this->createMock(ClassMetadata::class); - $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->willReturn(true); + $classMetadata = new ClassMetadata('Acme\Entity\Foo'); - $mapping = ['joinColumns' => [[]]]; - $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->willReturn($mapping); + if (class_exists(ManyToOneAssociationMapping::class)) { + $associationMapping = new ManyToOneAssociationMapping('field', 'Acme\Entity\Foo', 'Acme\Entity\Bar'); + $associationMapping->joinColumns[] = new JoinColumnMapping('field', 'field'); + } else { + $associationMapping = ['joinColumns' => [[]]]; + } + $classMetadata->associationMappings['field'] = $associationMapping; $this->assertEquals(new ValueGuess(false, Guess::HIGH_CONFIDENCE), $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); } public function testRequiredGuesserOneToOneExplicitNullable() { - $classMetadata = $this->createMock(ClassMetadata::class); - $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->willReturn(true); - - $mapping = ['joinColumns' => [['nullable' => true]]]; - $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->willReturn($mapping); + $classMetadata = new ClassMetadata('Acme\Entity\Foo'); + + if (class_exists(ManyToOneAssociationMapping::class)) { + $associationMapping = new ManyToOneAssociationMapping('field', 'Acme\Entity\Foo', 'Acme\Entity\Bar'); + $joinColumnMapping = new JoinColumnMapping('field', 'field'); + $joinColumnMapping->nullable = true; + $associationMapping->joinColumns[] = $joinColumnMapping; + } else { + $associationMapping = ['joinColumns' => [['nullable' => true]]]; + } + $classMetadata->associationMappings['field'] = $associationMapping; $this->assertEquals(new ValueGuess(false, Guess::HIGH_CONFIDENCE), $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); } public function testRequiredGuesserOneToOneNotNullable() { - $classMetadata = $this->createMock(ClassMetadata::class); - $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->willReturn(true); - - $mapping = ['joinColumns' => [['nullable' => false]]]; - $classMetadata->expects($this->once())->method('getAssociationMapping')->with('field')->willReturn($mapping); + $classMetadata = new ClassMetadata('Acme\Entity\Foo'); + + if (class_exists(ManyToOneAssociationMapping::class)) { + $associationMapping = new ManyToOneAssociationMapping('field', 'Acme\Entity\Foo', 'Acme\Entity\Bar'); + $joinColumnMapping = new JoinColumnMapping('field', 'field'); + $joinColumnMapping->nullable = false; + $associationMapping->joinColumns[] = $joinColumnMapping; + } else { + $associationMapping = ['joinColumns' => [['nullable' => false]]]; + } + $classMetadata->associationMappings['field'] = $associationMapping; $this->assertEquals(new ValueGuess(true, Guess::HIGH_CONFIDENCE), $this->getGuesser($classMetadata)->guessRequired('TestEntity', 'field')); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php index 2e9ed80e3115a..d0430cbaeef48 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..1a9ce2c693a51 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; @@ -23,11 +26,11 @@ class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase { - private $connection; - private $entityManager; - private $managerRegistry; - private $middleware; - private $entityManagerName = 'default'; + private Connection&MockObject $connection; + private EntityManagerInterface&MockObject $entityManager; + private ManagerRegistry&MockObject $managerRegistry; + private DoctrinePingConnectionMiddleware $middleware; + private string $entityManagerName = 'default'; protected function setUp(): void { @@ -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,16 @@ 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()); } + + private function mockPlatform(): AbstractPlatform&MockObject + { + $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 deeef93e7e13e..caaf8f3022552 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Middleware/Debug/MiddlewareTest.php @@ -191,7 +191,10 @@ public function testTransaction(callable $endTransactionMethod, string $expected { $this->init(); - $this->conn->setNestTransactionsWithSavepoints(true); + if (\defined('Doctrine\DBAL\Connection::PARAM_STR_ARRAY')) { + // DBAL < 4 + $this->conn->setNestTransactionsWithSavepoints(true); + } $this->conn->beginTransaction(); $this->conn->beginTransaction(); $this->conn->executeStatement('INSERT INTO products(name, price, stock) VALUES ("product1", 12.5, 5)'); @@ -225,20 +228,16 @@ public static function provideExecuteAndEndTransactionMethods(): array { return [ 'commit and exec' => [ - static fn (Connection $conn, string $sql) => $conn->executeStatement($sql), - static fn (Connection $conn) => $conn->commit(), + static fn (Connection $conn, string $sql): int|string => $conn->executeStatement($sql), + static fn (Connection $conn): ?bool => $conn->commit(), ], 'rollback and query' => [ - static fn (Connection $conn, string $sql) => $conn->executeQuery($sql), - static fn (Connection $conn) => $conn->rollBack(), + static fn (Connection $conn, string $sql): Result => $conn->executeQuery($sql), + static fn (Connection $conn): ?bool => $conn->rollBack(), ], 'prepared statement' => [ - static function (Connection $conn, string $sql): Result { - return $conn->prepare($sql)->executeQuery(); - }, - static function (Connection $conn): bool { - return $conn->commit(); - }, + static fn (Connection $conn, string $sql): Result => $conn->prepare($sql)->executeQuery(), + static fn (Connection $conn): ?bool => $conn->commit(), ], ]; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 296fbcc7dca59..76c735e672255 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -23,9 +23,11 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEnum; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation; +use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumInt; use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\EnumString; use Symfony\Component\PropertyInfo\Type; @@ -35,7 +37,7 @@ */ class DoctrineExtractorTest extends TestCase { - private function createExtractor() + private function createExtractor(): DoctrineExtractor { $config = ORMSetup::createConfiguration(true); $config->setMetadataDriverImpl(new AttributeDriver([__DIR__.'/../Tests/Fixtures' => 'Symfony\Bridge\Doctrine\Tests\Fixtures'], true)); @@ -116,7 +118,7 @@ public function testTestGetPropertiesWithEmbedded() /** * @dataProvider typesProvider */ - public function testExtract($property, array $type = null) + public function testExtract(string $property, array $type = null) { $this->assertEquals($type, $this->createExtractor()->getTypes(DoctrineDummy::class, $property, [])); } @@ -126,11 +128,11 @@ public function testExtractWithEmbedded() $expectedTypes = [new Type( Type::BUILTIN_TYPE_OBJECT, false, - 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable' + DoctrineEmbeddable::class )]; $actualTypes = $this->createExtractor()->getTypes( - 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded', + DoctrineWithEmbedded::class, 'embedded', [] ); @@ -150,9 +152,9 @@ public function testExtractEnum() $this->assertNull($this->createExtractor()->getTypes(DoctrineEnum::class, 'enumCustom', [])); } - public static function typesProvider() + public static function typesProvider(): array { - $provider = [ + return [ ['id', [new Type(Type::BUILTIN_TYPE_INT)]], ['guid', [new Type(Type::BUILTIN_TYPE_STRING)]], ['bigint', [new Type(Type::BUILTIN_TYPE_STRING)]], @@ -235,8 +237,6 @@ public static function typesProvider() )]], ['json', null], ]; - - return $provider; } public function testGetPropertiesCatchException() diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php index 13f1c67269692..7321ddd30e814 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/MessengerTransportDoctrineSchemaListenerTest.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); @@ -81,6 +85,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/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index d0becddbc76f3..9275dc46bd11f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Security\User; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\ObjectManager; @@ -251,12 +252,12 @@ private function createSchema($em) } } -abstract class UserLoaderRepository implements ObjectRepository, UserLoaderInterface +abstract class UserLoaderRepository extends EntityRepository implements UserLoaderInterface { abstract public function loadUserByIdentifier(string $identifier): ?UserInterface; } -abstract class PasswordUpgraderRepository implements ObjectRepository, PasswordUpgraderInterface +abstract class PasswordUpgraderRepository extends EntityRepository implements PasswordUpgraderInterface { abstract public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php index 04021b7fd2ea0..c7a3a94f86513 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Tests/TestRepositoryFactory.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Doctrine\Tests; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Repository\RepositoryFactory; -use Doctrine\Persistence\ObjectRepository; /** * @author Andreas Braun @@ -21,25 +21,25 @@ final class TestRepositoryFactory implements RepositoryFactory { /** - * @var array + * @var array */ private array $repositoryList = []; - public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository + public function getRepository(EntityManagerInterface $entityManager, $entityName): EntityRepository { $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); return $this->repositoryList[$repositoryHash] ??= $this->createRepository($entityManager, $entityName); } - public function setRepository(EntityManagerInterface $entityManager, string $entityName, ObjectRepository $repository): void + public function setRepository(EntityManagerInterface $entityManager, string $entityName, EntityRepository $repository): void { $repositoryHash = $this->getRepositoryHash($entityManager, $entityName); $this->repositoryList[$repositoryHash] = $repository; } - private function createRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository + private function createRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository { $metadata = $entityManager->getClassMetadata($entityName); $repositoryClassName = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php index c1db2bbe70124..7d0aec451577d 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; @@ -25,6 +25,8 @@ // DBAL 2 compatibility class_exists('Doctrine\DBAL\Platforms\PostgreSqlPlatform'); +// DBAL 3 compatibility +class_exists('Doctrine\DBAL\Platforms\SqlitePlatform'); final class UlidTypeTest extends TestCase { @@ -144,7 +146,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)) { diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index eb49736bac5f2..aa41a25125867 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -14,6 +14,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Tools\SchemaTool; use Doctrine\Persistence\ManagerRegistry; @@ -114,7 +115,9 @@ protected function createEntityManagerMock($repositoryMock) ->willReturn($repositoryMock) ; - $classMetadata = $this->createMock(ClassMetadataInfo::class); + $classMetadata = $this->createMock( + class_exists(ClassMetadataInfo::class) ? ClassMetadataInfo::class : ClassMetadata::class + ); $classMetadata ->expects($this->any()) ->method('hasField') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index e09da3634ac7c..18caca3f0c660 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Validator; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\BaseUser; @@ -39,9 +40,12 @@ class DoctrineLoaderTest extends TestCase { public function testLoadClassMetadata() { - $validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + $validatorBuilder = Validation::createValidatorBuilder()->enableAnnotationMapping(true); + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() ; @@ -146,10 +150,15 @@ public function testExtractEnum() $this->markTestSkipped('The "enumType" requires doctrine/orm 2.11.'); } - $validator = Validation::createValidatorBuilder() + $validatorBuilder = Validation::createValidatorBuilder() ->addMethodMapping('loadValidatorMetadata') - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + ->enableAnnotationMapping(true); + + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() ; @@ -165,9 +174,13 @@ public function testExtractEnum() public function testFieldMappingsConfiguration() { - $validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + $validatorBuilder = Validation::createValidatorBuilder()->enableAnnotationMapping(true); + + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addXmlMappings([__DIR__.'/../Resources/validator/BaseUser.xml']) ->addLoader( new DoctrineLoader( @@ -195,7 +208,7 @@ public function testClassValidator(bool $expected, string $classValidatorRegexp $this->assertSame($expected, $doctrineLoader->loadClassMetadata($classMetadata)); } - public static function regexpProvider() + public static function regexpProvider(): array { return [ [false, null], @@ -207,9 +220,13 @@ public static function regexpProvider() public function testClassNoAutoMapping() { - $validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping(true) - ->addDefaultDoctrineAnnotationReader() + $validatorBuilder = Validation::createValidatorBuilder()->enableAnnotationMapping(true); + + if (class_exists(AnnotationDriver::class) && method_exists($validatorBuilder, 'addDefaultDoctrineAnnotationReader')) { + $validatorBuilder->addDefaultDoctrineAnnotationReader(); + } + + $validator = $validatorBuilder ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{.*}')) ->getValidator(); diff --git a/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php b/src/Symfony/Bridge/Doctrine/Types/AbstractUidType.php index 6d7aac1487704..c73d628ce13d4 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; @@ -30,7 +32,7 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st } return $platform->getBinaryTypeDeclarationSQL([ - 'length' => '16', + 'length' => 16, 'fixed' => true, ]); } @@ -45,13 +47,13 @@ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Ab } 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); } } @@ -71,13 +73,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) { - throw ConversionException::conversionFailed($value, $this->getName()); + } catch (\InvalidArgumentException $e) { + $this->throwValueNotConvertible($value, $e); } } @@ -95,4 +97,22 @@ private function hasNativeGuidType(AbstractPlatform $platform): bool return $platform->getGuidTypeDeclarationSQL([]) !== $platform->$method(['fixed' => true, 'length' => 36]); } + + private function throwInvalidType(mixed $value): never + { + 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]); + } + + private function throwValueNotConvertible(mixed $value, \Throwable $previous): never + { + if (!class_exists(ValueNotConvertible::class)) { + throw ConversionException::conversionFailed($value, $this->getName(), previous: $previous); + } + + throw ValueNotConvertible::new($value, $this->getName(), previous: $previous); + } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index ce4773c5cfa4d..473405287203a 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Doctrine\Validator; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata as OrmClassMetadata; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Doctrine\Persistence\Mapping\MappingException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; @@ -51,7 +51,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool return false; } - if (!$doctrineMetadata instanceof ClassMetadataInfo) { + if (!$doctrineMetadata instanceof OrmClassMetadata) { return false; } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 7829a00e75d7b..130d2cc21b2b1 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -46,8 +46,8 @@ "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.12", + "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/orm": "^2.12|^3", "psr/log": "^1|^2|^3" }, "conflict": { diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php b/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php index bb73d8d0cf240..f0d97724a4e3f 100644 --- a/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php +++ b/src/Symfony/Component/Cache/Tests/Fixtures/DriverWrapper.php @@ -15,6 +15,7 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\ServerVersionProvider; class DriverWrapper implements Driver { @@ -31,9 +32,9 @@ public function connect(array $params, $username = null, $password = null, array return $this->driver->connect($params, $username, $password, $driverOptions); } - public function getDatabasePlatform(): AbstractPlatform + public function getDatabasePlatform(ServerVersionProvider $versionProvider = null): AbstractPlatform { - return $this->driver->getDatabasePlatform(); + return $this->driver->getDatabasePlatform($versionProvider); } public function getSchemaManager(Connection $conn, AbstractPlatform $platform): AbstractSchemaManager diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 45a35d4dd2e99..efec372c5ef50 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -30,7 +30,7 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "symfony/config": "^5.4|^6.0", diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index 248bcbb163c39..130c7282a29db 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -22,7 +22,7 @@ "symfony/polyfill-php83": "^1.27" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", "symfony/cache": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php index 514e140530f89..a66cd037d9ffc 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php @@ -145,13 +145,22 @@ public function testCreatesTableInTransaction(string $platform) $store->save($key); } - public static function providePlatforms() + public static function providePlatforms(): \Generator { yield [\Doctrine\DBAL\Platforms\PostgreSQLPlatform::class]; - yield [\Doctrine\DBAL\Platforms\PostgreSQL94Platform::class]; + + // DBAL < 4 + if (class_exists(\Doctrine\DBAL\Platforms\PostgreSQL94Platform::class)) { + yield [\Doctrine\DBAL\Platforms\PostgreSQL94Platform::class]; + } + yield [\Doctrine\DBAL\Platforms\SqlitePlatform::class]; yield [\Doctrine\DBAL\Platforms\SQLServerPlatform::class]; - yield [\Doctrine\DBAL\Platforms\SQLServer2012Platform::class]; + + // DBAL < 4 + if (class_exists(\Doctrine\DBAL\Platforms\SQLServer2012Platform::class)) { + yield [\Doctrine\DBAL\Platforms\SQLServer2012Platform::class]; + } } public function testTableCreationInTransactionNotSupported() diff --git a/src/Symfony/Component/Lock/composer.json b/src/Symfony/Component/Lock/composer.json index 80a76a1b00c5f..57f7a49bee287 100644 --- a/src/Symfony/Component/Lock/composer.json +++ b/src/Symfony/Component/Lock/composer.json @@ -21,7 +21,7 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "doctrine/dbal": "^2.13|^3.0", + "doctrine/dbal": "^2.13|^3|^4", "predis/predis": "^1.1|^2.0" }, "conflict": { diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index 3dae29a4cbd66..13a3bdc4110cd 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -17,8 +17,10 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQL57Platform; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -98,7 +100,7 @@ public function testItThrowsATransportExceptionIfItCannotAcknowledgeMessage() { $this->expectException(TransportException::class); $driverConnection = $this->getDBALConnectionMock(); - $driverConnection->method('delete')->willThrowException(new DBALException()); + $driverConnection->method('delete')->willThrowException($this->createStub(DBALException::class)); $connection = new Connection([], $driverConnection); $connection->ack('dummy_id'); @@ -108,7 +110,7 @@ public function testItThrowsATransportExceptionIfItCannotRejectMessage() { $this->expectException(TransportException::class); $driverConnection = $this->getDBALConnectionMock(); - $driverConnection->method('delete')->willThrowException(new DBALException()); + $driverConnection->method('delete')->willThrowException($this->createStub(DBALException::class)); $connection = new Connection([], $driverConnection); $connection->reject('dummy_id'); @@ -385,7 +387,7 @@ public function testGeneratedSql(AbstractPlatform $platform, string $expectedSql public static function providePlatformSql(): iterable { yield 'MySQL' => [ - new MySQL57Platform(), + class_exists(MySQLPlatform::class) ? new MySQLPlatform() : new MySQL57Platform(), 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC LIMIT 1 FOR UPDATE', ]; @@ -397,14 +399,23 @@ public static function providePlatformSql(): iterable } yield 'SQL Server' => [ - new SQLServer2012Platform(), + class_exists(SQLServerPlatform::class) && !class_exists(SQLServer2012Platform::class) ? new SQLServerPlatform() : new SQLServer2012Platform(), 'SELECT m.* FROM messenger_messages m WITH (UPDLOCK, ROWLOCK) WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ', ]; - yield 'Oracle' => [ - new OraclePlatform(), - 'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT a.id FROM (SELECT m.id FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC) a WHERE ROWNUM <= 1) FOR UPDATE', - ]; + if (!class_exists(MySQL57Platform::class)) { + // DBAL >= 4 + yield 'Oracle' => [ + new OraclePlatform(), + 'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT m.id FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC FETCH NEXT 1 ROWS ONLY) FOR UPDATE', + ]; + } else { + // DBAL < 4 + yield 'Oracle' => [ + new OraclePlatform(), + 'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT a.id FROM (SELECT m.id FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY available_at ASC) a WHERE ROWNUM <= 1) FOR UPDATE', + ]; + } } public function testConfigureSchema() @@ -473,7 +484,7 @@ public function testFindAllSqlGenerated(AbstractPlatform $platform, string $expe public function provideFindAllSqlGeneratedByPlatform(): iterable { yield 'MySQL' => [ - new MySQL57Platform(), + class_exists(MySQLPlatform::class) ? new MySQLPlatform() : new MySQL57Platform(), 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) LIMIT 50', ]; @@ -485,13 +496,22 @@ public function provideFindAllSqlGeneratedByPlatform(): iterable } yield 'SQL Server' => [ - new SQLServer2012Platform(), + class_exists(SQLServerPlatform::class) && !class_exists(SQLServer2012Platform::class) ? new SQLServerPlatform() : new SQLServer2012Platform(), 'SELECT m.* FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) ORDER BY (SELECT 0) OFFSET 0 ROWS FETCH NEXT 50 ROWS ONLY', ]; - yield 'Oracle' => [ - new OraclePlatform(), - 'SELECT a.* FROM (SELECT m.id AS "id", m.body AS "body", m.headers AS "headers", m.queue_name AS "queue_name", m.created_at AS "created_at", m.available_at AS "available_at", m.delivered_at AS "delivered_at" FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?)) a WHERE ROWNUM <= 50', - ]; + if (!class_exists(MySQL57Platform::class)) { + // DBAL >= 4 + yield 'Oracle' => [ + new OraclePlatform(), + 'SELECT m.id AS "id", m.body AS "body", m.headers AS "headers", m.queue_name AS "queue_name", m.created_at AS "created_at", m.available_at AS "available_at", m.delivered_at AS "delivered_at" FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?) FETCH NEXT 50 ROWS ONLY', + ]; + } else { + // DBAL < 4 + yield 'Oracle' => [ + new OraclePlatform(), + 'SELECT a.* FROM (SELECT m.id AS "id", m.body AS "body", m.headers AS "headers", m.queue_name AS "queue_name", m.created_at AS "created_at", m.available_at AS "available_at", m.delivered_at AS "delivered_at" FROM messenger_messages m WHERE (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) AND (m.queue_name = ?)) a WHERE ROWNUM <= 50', + ]; + } } } diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php index 6ef1734e6745b..65f9c685126cb 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrineTransportFactoryTest.php @@ -46,7 +46,11 @@ public function testCreateTransport() $schemaConfig = $this->createMock(SchemaConfig::class); $platform = $this->createMock(AbstractPlatform::class); $schemaManager->method('createSchemaConfig')->willReturn($schemaConfig); - $driverConnection->method('getSchemaManager')->willReturn($schemaManager); + $driverConnection->method( + method_exists(\Doctrine\DBAL\Connection::class, 'createSchemaManager') + ? 'createSchemaManager' + : 'getSchemaManager' + )->willReturn($schemaManager); $driverConnection->method('getDatabasePlatform')->willReturn($platform); $registry = $this->createMock(ConnectionRegistry::class); @@ -70,7 +74,11 @@ public function testCreateTransportNotifyWithPostgreSQLPlatform() $schemaConfig = $this->createMock(SchemaConfig::class); $platform = $this->createMock(PostgreSQLPlatform::class); $schemaManager->method('createSchemaConfig')->willReturn($schemaConfig); - $driverConnection->method('getSchemaManager')->willReturn($schemaManager); + $driverConnection->method( + method_exists(\Doctrine\DBAL\Connection::class, 'createSchemaManager') + ? 'createSchemaManager' + : 'getSchemaManager' + )->willReturn($schemaManager); $driverConnection->method('getDatabasePlatform')->willReturn($platform); $driverConnection->method('executeStatement')->willReturn(1); $registry = $this->createMock(ConnectionRegistry::class); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 85f95e088ffe5..c878fde5cd379 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -178,11 +178,8 @@ public function get(): ?array // Append pessimistic write lock to FROM clause if db platform supports it $sql = $query->getSQL(); - if (($fromPart = $query->getQueryPart('from')) - && ($table = $fromPart[0]['table'] ?? null) - && ($alias = $fromPart[0]['alias'] ?? null) - ) { - $fromClause = sprintf('%s %s', $table, $alias); + if (preg_match('/FROM (.+) WHERE/', (string) $sql, $matches)) { + $fromClause = $matches[1]; $sql = str_replace( sprintf('FROM %s WHERE', $fromClause), sprintf('FROM %s WHERE', $this->driverConnection->getDatabasePlatform()->appendLockHint($fromClause, LockMode::PESSIMISTIC_WRITE)), diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json index ba116bdba0801..433c8773b7db3 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=8.1", - "doctrine/dbal": "^2.13|^3.0", + "doctrine/dbal": "^2.13|^3|^4", "symfony/messenger": "^5.4|^6.0", "symfony/service-contracts": "^2.5|^3" }, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy