From 20d3a451ed94db52b32da8ab4760bd213c308f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Thu, 27 Jun 2024 23:22:59 +0200 Subject: [PATCH] Implement compatibility with Persistence 4 --- composer.json | 2 +- phpcs.xml.dist | 2 ++ phpstan-baseline.neon | 10 ++++++ phpstan-dbal3.neon | 3 ++ phpstan.neon | 3 ++ psalm-baseline.xml | 23 ++++++++---- src/EntityManager.php | 4 +-- src/Exception/NotSupported.php | 16 ++++++++- src/Mapping/ClassMetadata.php | 12 ++----- src/Mapping/Driver/DatabaseDriver.php | 7 ++++ .../Driver/LoadMappingFileImplementation.php | 35 +++++++++++++++++++ src/Mapping/Driver/XmlDriver.php | 8 ++--- .../GetReflectionClassImplementation.php | 33 +++++++++++++++++ .../Mock/NonProxyLoadingEntityManager.php | 8 +++++ tests/Tests/Mocks/MetadataDriverMock.php | 6 ++-- .../ORM/Functional/DatabaseDriverTestCase.php | 11 ++++++ .../ORM/Functional/Ticket/DDC3103Test.php | 14 ++++++-- tests/Tests/ORM/Mapping/ClassMetadataTest.php | 5 +++ 18 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 src/Mapping/Driver/LoadMappingFileImplementation.php create mode 100644 src/Mapping/GetReflectionClassImplementation.php diff --git a/composer.json b/composer.json index ec2ff1fd5a7..7269a3c08a1 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", "doctrine/lexer": "^3", - "doctrine/persistence": "^3.3.1", + "doctrine/persistence": "^3.3.1 || ^4", "psr/cache": "^1 || ^2 || ^3", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/var-exporter": "^6.3.9 || ^7.0" diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 4fde4cbf5e5..b14bbc6c1d8 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -48,6 +48,8 @@ + src/Mapping/Driver/LoadMappingFileImplementation.php + src/Mapping/GetReflectionClassImplementation.php tests/* diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7c8ec81ae84..3d587f68de7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -120,11 +120,21 @@ parameters: count: 1 path: src/EntityRepository.php + - + message: "#^If condition is always true\\.$#" + count: 1 + path: src/Mapping/ClassMetadata.php + - message: "#^If condition is always true\\.$#" count: 1 path: src/Mapping/ClassMetadataFactory.php + - + message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\Driver\\\\XmlDriver\\:\\:doLoadMappingFile\\(\\) is unused\\.$#" + count: 1 + path: src/Mapping/Driver/XmlDriver.php + - message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ToOneOwningSideMapping\\:\\:fromMappingArray\\(\\) should return static\\(Doctrine\\\\ORM\\\\Mapping\\\\ToOneOwningSideMapping\\) but returns Doctrine\\\\ORM\\\\Mapping\\\\ManyToOneAssociationMapping\\|Doctrine\\\\ORM\\\\Mapping\\\\OneToOneOwningSideMapping\\.$#" count: 1 diff --git a/phpstan-dbal3.neon b/phpstan-dbal3.neon index 724fe2003f7..1a6f32e0175 100644 --- a/phpstan-dbal3.neon +++ b/phpstan-dbal3.neon @@ -3,6 +3,9 @@ includes: - phpstan-params.neon parameters: + excludePaths: + - src/Mapping/Driver/LoadMappingFileImplementation.php + - src/Mapping/GetReflectionClassImplementation.php ignoreErrors: # Symfony cache supports passing a key prefix to the clear method. - '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/' diff --git a/phpstan.neon b/phpstan.neon index d90ec9fe41f..1475768e6ad 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,9 @@ includes: - phpstan-params.neon parameters: + excludePaths: + - src/Mapping/Driver/LoadMappingFileImplementation.php + - src/Mapping/GetReflectionClassImplementation.php ignoreErrors: # Symfony cache supports passing a key prefix to the clear method. - '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/' diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c7897f4d614..cef61546b74 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -298,15 +298,9 @@ - - - - - reflClass]]> - @@ -340,6 +334,7 @@ + reflClass]]> @@ -474,6 +469,14 @@ + + + + + + + + @@ -526,6 +529,14 @@ + + + + + + + + diff --git a/src/EntityManager.php b/src/EntityManager.php index eb5a123d0b6..5324d9cac7b 100644 --- a/src/EntityManager.php +++ b/src/EntityManager.php @@ -565,9 +565,9 @@ public function initializeObject(object $obj): void /** * {@inheritDoc} */ - public function isUninitializedObject($obj): bool + public function isUninitializedObject($value): bool { - return $this->unitOfWork->isUninitializedObject($obj); + return $this->unitOfWork->isUninitializedObject($value); } public function getFilters(): FilterCollection diff --git a/src/Exception/NotSupported.php b/src/Exception/NotSupported.php index 9192f87520e..6ae88f191cd 100644 --- a/src/Exception/NotSupported.php +++ b/src/Exception/NotSupported.php @@ -8,14 +8,15 @@ use function sprintf; -/** @deprecated */ final class NotSupported extends LogicException implements ORMException { + /** @deprecated */ public static function create(): self { return new self('This behaviour is (currently) not supported by Doctrine 2'); } + /** @deprecated */ public static function createForDbal3(string $context): self { return new self(sprintf( @@ -29,6 +30,7 @@ public static function createForDbal3(string $context): self )); } + /** @deprecated */ public static function createForPersistence3(string $context): self { return new self(sprintf( @@ -41,4 +43,16 @@ public static function createForPersistence3(string $context): self $context, )); } + + public static function createForPersistence4(string $context): self + { + return new self(sprintf( + <<<'EXCEPTION' + Context: %s + Problem: Feature was deprecated in doctrine/persistence 3.x and is not supported by installed doctrine/persistence:4.x + Solution: See the doctrine/deprecations logs for new alternative approaches. + EXCEPTION, + $context, + )); + } } diff --git a/src/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php index 7c9020805a5..b91bd9b5ac1 100644 --- a/src/Mapping/ClassMetadata.php +++ b/src/Mapping/ClassMetadata.php @@ -73,6 +73,8 @@ */ class ClassMetadata implements PersistenceClassMetadata, Stringable { + use GetReflectionClassImplementation; + /* The inheritance mapping types */ /** * NONE means the class does not participate in an inheritance hierarchy @@ -932,16 +934,6 @@ public function validateLifecycleCallbacks(ReflectionService $reflService): void } } - /** - * {@inheritDoc} - * - * Can return null when using static reflection, in violation of the LSP - */ - public function getReflectionClass(): ReflectionClass|null - { - return $this->reflClass; - } - /** @psalm-param array{usage?: mixed, region?: mixed} $cache */ public function enableCache(array $cache): void { diff --git a/src/Mapping/Driver/DatabaseDriver.php b/src/Mapping/Driver/DatabaseDriver.php index d012c0a85bd..d5fedc028f3 100644 --- a/src/Mapping/Driver/DatabaseDriver.php +++ b/src/Mapping/Driver/DatabaseDriver.php @@ -12,10 +12,12 @@ use Doctrine\DBAL\Types\Types; use Doctrine\Inflector\Inflector; use Doctrine\Inflector\InflectorFactory; +use Doctrine\ORM\Exception\NotSupported; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata; use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use Doctrine\Persistence\Mapping\StaticReflectionService; use InvalidArgumentException; use TypeError; @@ -23,6 +25,7 @@ use function array_keys; use function array_merge; use function assert; +use function class_exists; use function count; use function current; use function get_debug_type; @@ -79,6 +82,10 @@ class DatabaseDriver implements MappingDriver public function __construct(private readonly AbstractSchemaManager $sm) { + if (! class_exists(StaticReflectionService::class)) { + throw NotSupported::createForPersistence4('StaticReflectionService'); + } + $this->inflector = InflectorFactory::create()->build(); } diff --git a/src/Mapping/Driver/LoadMappingFileImplementation.php b/src/Mapping/Driver/LoadMappingFileImplementation.php new file mode 100644 index 00000000000..98f7933f8e9 --- /dev/null +++ b/src/Mapping/Driver/LoadMappingFileImplementation.php @@ -0,0 +1,35 @@ +doLoadMappingFile($file); + } + } +} else { + /** @internal */ + trait LoadMappingFileImplementation + { + /** + * {@inheritDoc} + */ + protected function loadMappingFile($file): array + { + return $this->doLoadMappingFile($file); + } + } +} diff --git a/src/Mapping/Driver/XmlDriver.php b/src/Mapping/Driver/XmlDriver.php index 30e85900e2e..e11b6b61d6f 100644 --- a/src/Mapping/Driver/XmlDriver.php +++ b/src/Mapping/Driver/XmlDriver.php @@ -43,6 +43,8 @@ */ class XmlDriver extends FileDriver { + use LoadMappingFileImplementation; + public const DEFAULT_FILE_EXTENSION = '.dcm.xml'; /** @@ -878,10 +880,8 @@ private function getCascadeMappings(SimpleXMLElement $cascadeElement): array return $cascades; } - /** - * {@inheritDoc} - */ - protected function loadMappingFile($file) + /** @return array */ + private function doLoadMappingFile(string $file): array { $this->validateMapping($file); $result = []; diff --git a/src/Mapping/GetReflectionClassImplementation.php b/src/Mapping/GetReflectionClassImplementation.php new file mode 100644 index 00000000000..ce4ee706cd5 --- /dev/null +++ b/src/Mapping/GetReflectionClassImplementation.php @@ -0,0 +1,33 @@ +reflClass; + } + } +} else { + trait GetReflectionClassImplementation + { + public function getReflectionClass(): ReflectionClass + { + return $this->reflClass; + } + } +} diff --git a/tests/Performance/Mock/NonProxyLoadingEntityManager.php b/tests/Performance/Mock/NonProxyLoadingEntityManager.php index 20f233e0089..bff330ab9be 100644 --- a/tests/Performance/Mock/NonProxyLoadingEntityManager.php +++ b/tests/Performance/Mock/NonProxyLoadingEntityManager.php @@ -212,4 +212,12 @@ public function contains(object $object): bool { return $this->realEntityManager->contains($object); } + + /** + * {@inheritDoc} + */ + public function isUninitializedObject($value): bool + { + return $this->realEntityManager->isUninitializedObject($value); + } } diff --git a/tests/Tests/Mocks/MetadataDriverMock.php b/tests/Tests/Mocks/MetadataDriverMock.php index b3be873c596..a2472cf8c46 100644 --- a/tests/Tests/Mocks/MetadataDriverMock.php +++ b/tests/Tests/Mocks/MetadataDriverMock.php @@ -15,14 +15,14 @@ class MetadataDriverMock implements MappingDriver /** * {@inheritDoc} */ - public function loadMetadataForClass($className, ClassMetadata $metadata) + public function loadMetadataForClass($className, ClassMetadata $metadata): void { } /** * {@inheritDoc} */ - public function isTransient($className) + public function isTransient($className): bool { return false; } @@ -30,7 +30,7 @@ public function isTransient($className) /** * {@inheritDoc} */ - public function getAllClassNames() + public function getAllClassNames(): array { return []; } diff --git a/tests/Tests/ORM/Functional/DatabaseDriverTestCase.php b/tests/Tests/ORM/Functional/DatabaseDriverTestCase.php index abbf26c3ed6..68d5d27a2af 100644 --- a/tests/Tests/ORM/Functional/DatabaseDriverTestCase.php +++ b/tests/Tests/ORM/Functional/DatabaseDriverTestCase.php @@ -6,10 +6,12 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Driver\DatabaseDriver; +use Doctrine\Persistence\Mapping\StaticReflectionService; use Doctrine\Tests\OrmFunctionalTestCase; use function array_keys; use function array_map; +use function class_exists; use function count; use function implode; use function in_array; @@ -20,6 +22,15 @@ */ abstract class DatabaseDriverTestCase extends OrmFunctionalTestCase { + protected function setUp(): void + { + if (! class_exists(StaticReflectionService::class)) { + self::markTestSkipped('This test is not supported by the current installed doctrine/persistence version'); + } + + parent::setUp(); + } + /** @psalm-return array */ protected function convertToClassMetadata(array $entityTables, array $manyTables = []): array { diff --git a/tests/Tests/ORM/Functional/Ticket/DDC3103Test.php b/tests/Tests/ORM/Functional/Ticket/DDC3103Test.php index ec505c07741..5ead8d58698 100644 --- a/tests/Tests/ORM/Functional/Ticket/DDC3103Test.php +++ b/tests/Tests/ORM/Functional/Ticket/DDC3103Test.php @@ -7,10 +7,12 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; +use Doctrine\Persistence\Mapping\StaticReflectionService; use Doctrine\Tests\OrmFunctionalTestCase; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; +use function class_exists; use function serialize; use function unserialize; @@ -18,6 +20,15 @@ #[Group('DDC-3103')] class DDC3103Test extends OrmFunctionalTestCase { + protected function setUp(): void + { + if (! class_exists(StaticReflectionService::class)) { + self::markTestSkipped('This test is not supported by the current installed doctrine/persistence version'); + } + + parent::setUp(); + } + public function testIssue(): void { $classMetadata = new ClassMetadata(DDC3103ArticleId::class); @@ -39,7 +50,6 @@ public function testIssue(): void #[Embeddable] class DDC3103ArticleId { - /** @var string */ #[Column(name: 'name', type: 'string', length: 255)] - protected $nameValue; + protected string $nameValue; } diff --git a/tests/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Tests/ORM/Mapping/ClassMetadataTest.php index 38b2bde7ca9..60b1bc1822f 100644 --- a/tests/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Tests/ORM/Mapping/ClassMetadataTest.php @@ -56,6 +56,7 @@ use stdClass; use function assert; +use function class_exists; use function count; use function serialize; use function str_contains; @@ -979,6 +980,10 @@ public function testCanInstantiateInternalPhpClassSubclassFromUnserializedMetada public function testWakeupReflectionWithEmbeddableAndStaticReflectionService(): void { + if (! class_exists(StaticReflectionService::class)) { + self::markTestSkipped('This test is not supported by the current installed doctrine/persistence version'); + } + $classMetadata = new ClassMetadata(TestEntity1::class); $classMetadata->mapEmbedded(