diff --git a/composer.json b/composer.json
index ec2ff1fd5a..7269a3c08a 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 4fde4cbf5e..b14bbc6c1d 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 7c8ec81ae8..1b6362a6d8 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -120,6 +120,11 @@ 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
diff --git a/phpstan-dbal3.neon b/phpstan-dbal3.neon
index 724fe2003f..889d94c1df 100644
--- a/phpstan-dbal3.neon
+++ b/phpstan-dbal3.neon
@@ -34,3 +34,8 @@ parameters:
-
message: '~deprecated class Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand\:~'
path: src/Tools/Console/ConsoleRunner.php
+
+ # Compatibility with Persistence 3
+ -
+ message: '#Expression on left side of \?\? is not nullable.#'
+ path: src/Mapping/Driver/AttributeDriver.php
diff --git a/phpstan.neon b/phpstan.neon
index d90ec9fe41..f98eb8d00b 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -45,3 +45,8 @@ parameters:
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
+
+ # Compatibility with Persistence 3
+ -
+ message: '#Expression on left side of \?\? is not nullable.#'
+ path: src/Mapping/Driver/AttributeDriver.php
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index c7897f4d61..cef61546b7 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 eb5a123d0b..5324d9cac7 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/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php
index 7c9020805a..b91bd9b5ac 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/LoadMappingFileImplementation.php b/src/Mapping/Driver/LoadMappingFileImplementation.php
new file mode 100644
index 0000000000..df351889ba
--- /dev/null
+++ b/src/Mapping/Driver/LoadMappingFileImplementation.php
@@ -0,0 +1,35 @@
+doLoadMappingFile($file);
+ }
+ }
+} else {
+ /** @internal */
+ trait LoadMappingFileImplementation
+ {
+ /**
+ * {@inheritDoc}
+ */
+ protected function loadMappingFile($file)
+ {
+ return $this->doLoadMappingFile($file);
+ }
+ }
+}
diff --git a/src/Mapping/Driver/XmlDriver.php b/src/Mapping/Driver/XmlDriver.php
index 30e85900e2..e11b6b61d6 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 0000000000..780015c368
--- /dev/null
+++ b/src/Mapping/GetReflectionClassImplementation.php
@@ -0,0 +1,33 @@
+reflClass;
+ }
+ }
+} else {
+ trait GetReflectionClassImplementation
+ {
+ /**
+ * {@inheritDoc}
+ *
+ * Can return null when using static reflection, in violation of the LSP
+ */
+ public function getReflectionClass(): ReflectionClass|null
+ {
+ return $this->reflClass;
+ }
+ }
+}
diff --git a/tests/Performance/Mock/NonProxyLoadingEntityManager.php b/tests/Performance/Mock/NonProxyLoadingEntityManager.php
index 20f233e008..bff330ab9b 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 b3be873c59..a2472cf8c4 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/Ticket/DDC3103Test.php b/tests/Tests/ORM/Functional/Ticket/DDC3103Test.php
index ec505c0774..5ead8d5869 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 38b2bde7ca..60b1bc1822 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(