From c2823b89309fb5db0d4515eff43c03073265907a 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 +++++++++++++++++ tests/Tests/Mocks/MetadataDriverMock.php | 6 ++-- .../ORM/Functional/DatabaseDriverTestCase.php | 9 +++++ .../ORM/Functional/Ticket/DDC3103Test.php | 12 +++++-- tests/Tests/ORM/Mapping/ClassMetadataTest.php | 5 +++ 17 files changed, 161 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 2a751261861..21b7a6e25da 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", "doctrine/lexer": "^3", - "doctrine/persistence": "^3.3.1", + "doctrine/persistence": "4.0.x-dev", "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 b9ef942c965..ac8d940093c 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 7a84afd4f83..b1208dd9659 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -300,15 +300,9 @@ - - - - - reflClass]]> - @@ -342,6 +336,7 @@ + reflClass]]> @@ -475,6 +470,14 @@ + + + + + + + + @@ -527,6 +530,14 @@ + + + + + + + + diff --git a/src/EntityManager.php b/src/EntityManager.php index 8045ac2f5e3..bb6d7460a62 100644 --- a/src/EntityManager.php +++ b/src/EntityManager.php @@ -560,9 +560,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 f58e00e72fe..bbba6ee00e9 100644 --- a/src/Mapping/ClassMetadata.php +++ b/src/Mapping/ClassMetadata.php @@ -70,6 +70,8 @@ */ class ClassMetadata implements PersistenceClassMetadata, Stringable { + use GetReflectionClassImplementation; + /* The inheritance mapping types */ /** * NONE means the class does not participate in an inheritance hierarchy @@ -929,16 +931,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 301e44a97e2..480c4fa979e 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 ff473ce3564..f706d619856 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'; /** @@ -876,10 +878,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/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 7a9039cfe11..b3b489714ae 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,13 @@ */ 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'); + } + } + /** @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..71ae78d1810 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,13 @@ #[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'); + } + } + public function testIssue(): void { $classMetadata = new ClassMetadata(DDC3103ArticleId::class); @@ -39,7 +48,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 a7fb88b2e03..d5c0fd204ed 100644 --- a/tests/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Tests/ORM/Mapping/ClassMetadataTest.php @@ -54,6 +54,7 @@ use stdClass; use function assert; +use function class_exists; use function count; use function serialize; use function str_contains; @@ -977,6 +978,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(