diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index db9c151f0268e..ccd53c1ebe7c6 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; @@ -49,7 +48,7 @@ public function getProperties(string $class, array $context = []) $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, function ($property) { return !str_contains($property, '.'); }); @@ -73,7 +72,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); @@ -86,11 +85,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 @@ -102,7 +100,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']); @@ -130,7 +127,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'])]; } @@ -141,7 +138,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); @@ -233,9 +230,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($associationMapping): bool { if (isset($associationMapping['id']) && $associationMapping['id']) { return false; diff --git a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php index 80964663c340c..f08e2154d06e9 100644 --- a/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php +++ b/src/Symfony/Bridge/Doctrine/Test/TestRepositoryFactory.php @@ -12,10 +12,36 @@ namespace Symfony\Bridge\Doctrine\Test; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\Persistence\ObjectRepository; +if ((new \ReflectionMethod(RepositoryFactory::class, 'getRepository'))->hasReturnType()) { + /** @internal */ + trait GetRepositoryTrait + { + public function getRepository(EntityManagerInterface $entityManager, string $entityName): EntityRepository + { + return $this->doGetRepository($entityManager, $entityName); + } + } +} else { + /** @internal */ + trait GetRepositoryTrait + { + /** + * {@inheritdoc} + * + * @return ObjectRepository + */ + public function getRepository(EntityManagerInterface $entityManager, $entityName) + { + return $this->doGetRepository($entityManager, $entityName); + } + } +} + /** * @author Andreas Braun * @@ -23,17 +49,14 @@ */ class TestRepositoryFactory implements RepositoryFactory { + use GetRepositoryTrait; + /** * @var ObjectRepository[] */ private $repositoryList = []; - /** - * {@inheritdoc} - * - * @return ObjectRepository - */ - public function getRepository(EntityManagerInterface $entityManager, $entityName) + private function doGetRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository { if (__CLASS__ === static::class) { trigger_deprecation('symfony/doctrine-bridge', '5.3', '"%s" is deprecated and will be removed in 6.0.', __CLASS__); 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/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index b9a9d6558a49d..67f600f5d145e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -13,14 +13,19 @@ 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\Version; +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; use Symfony\Bridge\Doctrine\Types\UuidType; use Symfony\Component\Form\Exception\TransformationFailedException; @@ -37,21 +42,19 @@ protected function tearDown(): void public function testIdentifierTypeIsStringArray() { - $this->checkIdentifierType('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity', class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY); + $this->checkIdentifierType(SingleStringIdEntity::class, class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY); } public function testIdentifierTypeIsIntegerArray() { - $this->checkIdentifierType('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity', class_exists(ArrayParameterType::class) ? ArrayParameterType::INTEGER : Connection::PARAM_INT_ARRAY); + $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') @@ -62,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(); @@ -82,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') @@ -95,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(); @@ -118,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') @@ -131,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(); @@ -163,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') @@ -176,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(); @@ -208,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(); @@ -229,17 +226,9 @@ public function testUidThrowProperException($entityClass) public function testEmbeddedIdentifierName() { - if (Version::compare('2.5.0') > 0) { - $this->markTestSkipped('Applicable only for Doctrine >= 2.5.0'); - - return; - } - $em = DoctrineTestHelper::createTestEntityManager(); - $query = $this->getMockBuilder(QueryMock::class) - ->onlyMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) - ->getMock(); + $query = $this->getQueryMock(); $query ->method('getResult') @@ -250,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(); @@ -259,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'], @@ -273,32 +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() - { - } /** - * @return array|string + * @return (LegacyQueryMock&MockObject)|(Query&MockObject) */ - public function getSQL() + private function getQueryMock(): AbstractQuery { - } + $class = ((new \ReflectionClass(Query::class))->isFinal()) ? LegacyQueryMock::class : Query::class; - /** - * @return Result|int - */ - protected function _doExecute() - { + 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/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index 40884d2c572c6..e4e67eb663557 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -26,9 +26,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; @@ -38,7 +40,7 @@ */ class DoctrineExtractorTest extends TestCase { - private function createExtractor() + private function createExtractor(): DoctrineExtractor { $config = class_exists(ORMSetup::class) ? ORMSetup::createConfiguration(true) @@ -109,10 +111,6 @@ public function testGetProperties() public function testTestGetPropertiesWithEmbedded() { - if (!class_exists(\Doctrine\ORM\Mapping\Embedded::class)) { - $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); - } - $this->assertEquals( [ 'id', @@ -125,25 +123,21 @@ 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, [])); } public function testExtractWithEmbedded() { - if (!class_exists(\Doctrine\ORM\Mapping\Embedded::class)) { - $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); - } - $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', [] ); @@ -166,9 +160,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)]], @@ -251,8 +245,6 @@ public static function typesProvider() )]], ['json', null], ]; - - return $provider; } public function testGetPropertiesCatchException() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index eb7b00d561173..f3a78dfe9226b 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/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index b04e51eb781e4..66849208fd44b 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 731f393c48b1d..9aa8d6ef61230 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; @@ -47,9 +48,12 @@ protected function setUp(): void 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() ; @@ -157,10 +161,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() ; @@ -176,9 +185,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( @@ -206,7 +219,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], @@ -218,9 +231,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/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index fe199c2043ff0..9fcb0d3486ada 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; @@ -54,7 +54,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 dcb046fbc38e7..61d08a0b213d2 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -46,8 +46,8 @@ "doctrine/annotations": "^1.10.4|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/orm": "^2.7.4|^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 91296b0477ba9..e3526bb8158b4 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -34,7 +34,7 @@ "require-dev": { "cache/integration-tests": "dev-master", "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1", "psr/simple-cache": "^1.0|^2.0", "symfony/config": "^4.4|^5.0|^6.0", diff --git a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php index f8abec2522319..9f8c2aac6be3b 100644 --- a/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/DoctrineDbalStoreTest.php @@ -150,13 +150,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 c2b8e3078e756..b7e2d0c0d87ea 100644 --- a/src/Symfony/Component/Lock/composer.json +++ b/src/Symfony/Component/Lock/composer.json @@ -22,7 +22,7 @@ "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "doctrine/dbal": "^2.13|^3.0", + "doctrine/dbal": "^2.13|^3|^4", "predis/predis": "~1.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 5af44c4845849..d7c9a05c8502e 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -19,8 +19,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'); @@ -391,7 +393,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', ]; @@ -403,14 +405,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() @@ -483,7 +494,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', ]; @@ -495,13 +506,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 54dd7ab153adf..6e108baa449be 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); $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 6bb601c2eef29..fe1b7a2f4ef91 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -177,11 +177,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 3a9494a6a24cb..e1490a7f98e2e 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/composer.json @@ -21,7 +21,7 @@ "symfony/service-contracts": "^1.1|^2|^3" }, "require-dev": { - "doctrine/dbal": "^2.13|^3.0", + "doctrine/dbal": "^2.13|^3|^4", "doctrine/persistence": "^1.3|^2|^3", "symfony/property-access": "^4.4|^5.0|^6.0", "symfony/serializer": "^4.4|^5.0|^6.0"