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" 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