From 2d65c008790407cafaf85439cfe13f3d4cdbd53f Mon Sep 17 00:00:00 2001 From: Benjamin Courtel Date: Wed, 5 Nov 2025 04:05:07 +0100 Subject: [PATCH 1/7] Fix missing PHP syntax highlighting on "Migrating Schemas" (#2899) --- docs/en/reference/migrating-schemas.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/reference/migrating-schemas.rst b/docs/en/reference/migrating-schemas.rst index 0b979572b0..e59c343278 100644 --- a/docs/en/reference/migrating-schemas.rst +++ b/docs/en/reference/migrating-schemas.rst @@ -41,6 +41,8 @@ To create the collections for all the document classes, you can use the For a specific document class, you can use the `createDocumentCollection()` method with the class name as an argument: +.. code-block:: php + createDocumentCollection(Person::class); @@ -48,6 +50,8 @@ method with the class name as an argument: Once the collection is created, you can also set up indexes with ``ensureIndexes``, and search indexes with ``createSearchIndexes``: +.. code-block:: php + ensureIndexes(); From 134756f5df3dd517ba5b2a05f37165de4d305012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 6 Nov 2025 07:06:17 -0500 Subject: [PATCH 2/7] Add attributes for every test classes with annotations (#2893) --- tests/Documents/FileWithoutMetadata.php | 1 + tests/Tests/Mapping/AbstractMappingDriverTestCase.php | 4 ++-- .../Mapping/Documents/GlobalNamespaceDocument.php | 10 ++++++++++ tests/Tests/Tools/GH1299/BaseUser.php | 3 +++ tests/Tests/Tools/GH1299/GH1299User.php | 2 ++ tests/Tests/Tools/GH297/Address.php | 2 ++ tests/Tests/Tools/GH297/AddressTrait.php | 3 +++ tests/Tests/Tools/GH297/Admin.php | 1 + tests/Tests/Tools/GH297/User.php | 3 +++ 9 files changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/Documents/FileWithoutMetadata.php b/tests/Documents/FileWithoutMetadata.php index be82621488..806276b2bf 100644 --- a/tests/Documents/FileWithoutMetadata.php +++ b/tests/Documents/FileWithoutMetadata.php @@ -18,6 +18,7 @@ class FileWithoutMetadata * * @var string|null */ + #[ODM\File\Filename] private $filename; public function getId(): ?string diff --git a/tests/Tests/Mapping/AbstractMappingDriverTestCase.php b/tests/Tests/Mapping/AbstractMappingDriverTestCase.php index 2b60e8d1f9..5d790898f9 100644 --- a/tests/Tests/Mapping/AbstractMappingDriverTestCase.php +++ b/tests/Tests/Mapping/AbstractMappingDriverTestCase.php @@ -1254,7 +1254,7 @@ class AbstractMappingDriverDuplicateDatabaseNameNotSaved extends AbstractMapping * * @var string|null */ - #[ODM\Field(type: 'int', name: 'baz')] + #[ODM\Field(type: 'string', name: 'baz')] public $foo; /** @@ -1262,7 +1262,7 @@ class AbstractMappingDriverDuplicateDatabaseNameNotSaved extends AbstractMapping * * @var string|null */ - #[ODM\Field(type: 'int', name: 'baz', notSaved: true)] + #[ODM\Field(type: 'string', name: 'baz', notSaved: true)] public $bar; } diff --git a/tests/Tests/Mapping/Documents/GlobalNamespaceDocument.php b/tests/Tests/Mapping/Documents/GlobalNamespaceDocument.php index 5e28402940..ef5a740d26 100644 --- a/tests/Tests/Mapping/Documents/GlobalNamespaceDocument.php +++ b/tests/Tests/Mapping/Documents/GlobalNamespaceDocument.php @@ -6,6 +6,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\Document */ +#[ODM\Document] class DoctrineGlobal_Article { /** @@ -13,6 +14,7 @@ class DoctrineGlobal_Article * * @var string|null */ + #[ODM\Id] protected $id; /** @@ -20,6 +22,7 @@ class DoctrineGlobal_Article * * @var string|null */ + #[ODM\Field(type: 'string')] protected $headline; /** @@ -27,6 +30,7 @@ class DoctrineGlobal_Article * * @var string|null */ + #[ODM\Field(type: 'string')] protected $text; /** @@ -34,6 +38,7 @@ class DoctrineGlobal_Article * * @var DoctrineGlobal_User|null */ + #[ODM\ReferenceMany(targetDocument: DoctrineGlobal_User::class)] protected $author; /** @@ -41,10 +46,12 @@ class DoctrineGlobal_Article * * @var Collection */ + #[ODM\ReferenceMany(targetDocument: DoctrineGlobal_User::class)] protected $editor; } /** @ODM\Document */ +#[ODM\Document] class DoctrineGlobal_User { /** @@ -52,6 +59,7 @@ class DoctrineGlobal_User * * @var string|null */ + #[ODM\Id] private $id; /** @@ -59,6 +67,7 @@ class DoctrineGlobal_User * * @var string */ + #[ODM\Field(type: 'string')] private $username; /** @@ -66,5 +75,6 @@ class DoctrineGlobal_User * * @var string */ + #[ODM\Field(type: 'string')] private $email; } diff --git a/tests/Tests/Tools/GH1299/BaseUser.php b/tests/Tests/Tools/GH1299/BaseUser.php index 9b1473d0cb..277c84c176 100644 --- a/tests/Tests/Tools/GH1299/BaseUser.php +++ b/tests/Tests/Tools/GH1299/BaseUser.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\Document */ +#[ODM\Document] class BaseUser { /** @@ -14,6 +15,7 @@ class BaseUser * * @var string|null */ + #[ODM\Id] protected $id; /** @@ -21,6 +23,7 @@ class BaseUser * * @var string|null */ + #[ODM\Field(type: 'string')] protected $name; public function getId(): ?string diff --git a/tests/Tests/Tools/GH1299/GH1299User.php b/tests/Tests/Tools/GH1299/GH1299User.php index 4d205b1612..64ed574e58 100644 --- a/tests/Tests/Tools/GH1299/GH1299User.php +++ b/tests/Tests/Tools/GH1299/GH1299User.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\Document */ +#[ODM\Document] class GH1299User extends BaseUser { /** @@ -14,5 +15,6 @@ class GH1299User extends BaseUser * * @var string|null */ + #[ODM\Field(type: 'string')] protected $lastname; } diff --git a/tests/Tests/Tools/GH297/Address.php b/tests/Tests/Tools/GH297/Address.php index 6b921ea0a6..5a0c11f266 100644 --- a/tests/Tests/Tools/GH297/Address.php +++ b/tests/Tests/Tools/GH297/Address.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\EmbeddedDocument */ +#[ODM\EmbeddedDocument] class Address { /** @@ -14,6 +15,7 @@ class Address * * @var string|null */ + #[ODM\Field(type: 'string')] private $street; public function getStreet(): ?string diff --git a/tests/Tests/Tools/GH297/AddressTrait.php b/tests/Tests/Tools/GH297/AddressTrait.php index 9c0ba02168..eea24926f8 100644 --- a/tests/Tests/Tools/GH297/AddressTrait.php +++ b/tests/Tests/Tools/GH297/AddressTrait.php @@ -4,6 +4,8 @@ namespace Doctrine\ODM\MongoDB\Tests\Tools\GH297; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; + trait AddressTrait { /** @@ -11,6 +13,7 @@ trait AddressTrait * * @var Address|null */ + #[ODM\EmbedOne] private $address; public function getAddress(): ?Address diff --git a/tests/Tests/Tools/GH297/Admin.php b/tests/Tests/Tools/GH297/Admin.php index fedea6f7ac..70011f5fa3 100644 --- a/tests/Tests/Tools/GH297/Admin.php +++ b/tests/Tests/Tools/GH297/Admin.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\Document */ +#[ODM\Document] class Admin extends User { } diff --git a/tests/Tests/Tools/GH297/User.php b/tests/Tests/Tools/GH297/User.php index d0f19462cd..9fd9bca8bf 100644 --- a/tests/Tests/Tools/GH297/User.php +++ b/tests/Tests/Tools/GH297/User.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; /** @ODM\Document */ +#[ODM\Document] class User { use AddressTrait; @@ -16,6 +17,7 @@ class User * * @var string|null */ + #[ODM\Id] private $id; /** @@ -23,6 +25,7 @@ class User * * @var string|null */ + #[ODM\Field(type: 'string')] private $name; public function getId(): ?string From 55d2cfdb312aefe3ac447fe133de98ae4fd83850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 31 Oct 2025 10:28:55 +0100 Subject: [PATCH 3/7] Ensure proxy-manager is not used when native lazy are enabled --- src/DocumentManager.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/DocumentManager.php b/src/DocumentManager.php index 7ed9e3e73b..f73be994d8 100644 --- a/src/DocumentManager.php +++ b/src/DocumentManager.php @@ -154,9 +154,23 @@ protected function __construct(?Client $client = null, ?Configuration $config = $this->config->getDriverOptions(), ); - $this->classNameResolver = $this->config->isLazyGhostObjectEnabled() - ? new CachingClassNameResolver(new LazyGhostProxyClassNameResolver()) - : new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config)); + if ($this->config->isNativeLazyObjectEnabled()) { + $this->classNameResolver = new class implements ClassNameResolver, ProxyClassNameResolver { + public function getRealClass(string $class): string + { + return $class; + } + + public function resolveClassName(string $className): string + { + return $className; + } + }; + } elseif ($this->config->isLazyGhostObjectEnabled()) { + $this->classNameResolver = new CachingClassNameResolver(new LazyGhostProxyClassNameResolver()); + } else { + $this->classNameResolver = new CachingClassNameResolver(new ProxyManagerClassNameResolver($this->config)); + } $metadataFactoryClassName = $this->config->getClassMetadataFactoryName(); $this->metadataFactory = new $metadataFactoryClassName(); From 5b7f4346b25d75d9acc71c5ba7be385bfddb3ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 31 Oct 2025 11:16:01 +0100 Subject: [PATCH 4/7] Add a test without optional dependencies --- .github/workflows/continuous-integration.yml | 16 ++++++++++++++++ tests/Tests/BaseTestCase.php | 9 +++++++-- tests/Tests/ConfigurationTest.php | 8 ++++++++ tests/Tests/Mapping/AnnotationDriverTest.php | 3 +++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c837bf2cb7..c996e5dec2 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -92,6 +92,15 @@ jobs: dependencies: "highest" symfony-version: "stable" proxy: "lazy-ghost" + # Test removing optional dependencies + - topology: "server" + php-version: "8.4" + mongodb-version: "8.0" + driver-version: "stable" + dependencies: "highest" + symfony-version: "stable" + proxy: "native" + remove-optional-dependencies: true # Test with a sharded cluster # Currently disabled due to a bug where MongoDB reports "sharding status unknown" # - topology: "sharded_cluster" @@ -148,6 +157,13 @@ jobs: composer require --no-update symfony/var-dumper:^${{ matrix.symfony-version }} composer require --no-update --dev symfony/cache:^${{ matrix.symfony-version }} + - name: "Remove optional dependencies" + if: "${{ matrix.remove-optional-dependencies }}" + run: | + composer remove --no-update friendsofphp/proxy-manager-lts symfony/var-exporter + composer remove --no-update --dev symfony/cache doctrine/orm doctrine/annotations + composer remove --no-update --dev doctrine/coding-standard phpstan/phpstan phpstan/phpstan-deprecation-rule phpstan/phpstan-phpunit + - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v3" with: diff --git a/tests/Tests/BaseTestCase.php b/tests/Tests/BaseTestCase.php index 43fec12308..b6a7fafc21 100644 --- a/tests/Tests/BaseTestCase.php +++ b/tests/Tests/BaseTestCase.php @@ -105,8 +105,13 @@ protected static function getConfiguration(): Configuration $config->setPersistentCollectionNamespace('PersistentCollections'); $config->setDefaultDB(DOCTRINE_MONGODB_DATABASE); $config->setMetadataDriverImpl(static::createMetadataDriverImpl()); - $config->setUseLazyGhostObject((bool) $_ENV['USE_LAZY_GHOST_OBJECT']); - $config->setUseNativeLazyObject((bool) $_ENV['USE_NATIVE_LAZY_OBJECT']); + if ($_ENV['USE_LAZY_GHOST_OBJECT']) { + $config->setUseLazyGhostObject(true); + } + + if ($_ENV['USE_NATIVE_LAZY_OBJECT']) { + $config->setUseNativeLazyObject(true); + } if ($config->isNativeLazyObjectEnabled()) { NativeLazyObjectFactory::enableTracking(); diff --git a/tests/Tests/ConfigurationTest.php b/tests/Tests/ConfigurationTest.php index c07d62c8f9..c39a738a3b 100644 --- a/tests/Tests/ConfigurationTest.php +++ b/tests/Tests/ConfigurationTest.php @@ -13,9 +13,11 @@ use PHPUnit\Framework\Attributes\RequiresPhp; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; +use ProxyManager\Configuration as ProxyManagerConfiguration; use stdClass; use function base64_encode; +use function class_exists; use function str_repeat; class ConfigurationTest extends TestCase @@ -38,6 +40,12 @@ public function testUseLazyGhostObject(): void self::assertFalse($c->isLazyGhostObjectEnabled()); $c->setUseLazyGhostObject(true); self::assertTrue($c->isLazyGhostObjectEnabled()); + + if (! class_exists(ProxyManagerConfiguration::class)) { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Package "friendsofphp/proxy-manager-lts" is required to disable LazyGhostObject.'); + } + $c->setUseLazyGhostObject(false); self::assertFalse($c->isLazyGhostObjectEnabled()); } diff --git a/tests/Tests/Mapping/AnnotationDriverTest.php b/tests/Tests/Mapping/AnnotationDriverTest.php index 55064a4ffa..3bad2754af 100644 --- a/tests/Tests/Mapping/AnnotationDriverTest.php +++ b/tests/Tests/Mapping/AnnotationDriverTest.php @@ -4,12 +4,14 @@ namespace Doctrine\ODM\MongoDB\Tests\Mapping; +use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; use Doctrine\ODM\MongoDB\Mapping\Annotations\Document; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; use Doctrine\Persistence\Mapping\Driver\FileClassLocator; use Doctrine\Persistence\Mapping\Driver\MappingDriver; +use PHPUnit\Framework\Attributes\RequiresMethod; use function call_user_func; use function class_exists; @@ -19,6 +21,7 @@ use const E_USER_DEPRECATED; +#[RequiresMethod(AnnotationReader::class, '__construct')] class AnnotationDriverTest extends AbstractAnnotationDriverTestCase { protected static function loadDriver(array $paths = []): MappingDriver From 211488a492f5092833b8e5867f9ceb67072f17fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 31 Oct 2025 13:33:01 +0100 Subject: [PATCH 5/7] Don't change lazyGhostObject config when setting nativeLazyObject --- src/Configuration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Configuration.php b/src/Configuration.php index 15b3703b6b..2af5df46e8 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -708,7 +708,8 @@ public function setUseLazyGhostObject(bool $flag): void public function isLazyGhostObjectEnabled(): bool { - return $this->lazyGhostObject; + // Always false if native lazy objects are enabled + return $this->lazyGhostObject && ! $this->nativeLazyObject; } public function setUseNativeLazyObject(bool $nativeLazyObject): void @@ -718,7 +719,6 @@ public function setUseNativeLazyObject(bool $nativeLazyObject): void } $this->nativeLazyObject = $nativeLazyObject; - $this->lazyGhostObject = ! $nativeLazyObject || $this->lazyGhostObject; } public function isNativeLazyObjectEnabled(): bool From a311fe12c54dd49ec75e76e2a8f38443a106f785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 31 Oct 2025 13:33:16 +0100 Subject: [PATCH 6/7] Don't use deprecated ClassMetadata::getPropertyAccessor() --- src/Proxy/Factory/StaticProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Proxy/Factory/StaticProxyFactory.php b/src/Proxy/Factory/StaticProxyFactory.php index b9df9191dd..fdf7b47963 100644 --- a/src/Proxy/Factory/StaticProxyFactory.php +++ b/src/Proxy/Factory/StaticProxyFactory.php @@ -146,7 +146,7 @@ private function skippedFieldsFqns(ClassMetadata $metadata): array $skippedFieldsFqns = []; foreach ($metadata->getIdentifierFieldNames() as $idField) { - $skippedFieldsFqns[] = $this->propertyFqcn($metadata->getReflectionProperty($idField)); + $skippedFieldsFqns[] = $this->propertyFqcn($metadata->getPropertyAccessor($idField)->getUnderlyingReflector()); } foreach ($metadata->getReflectionClass()->getProperties() as $property) { From 673e6dc388e290b1b75b609e4adc4bbe2549613e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 31 Oct 2025 21:57:33 +0100 Subject: [PATCH 7/7] Fix initialisation of ProxyManager objects when hydrated from a collection Fix test NestedCollectionsTest with ProxyManager Add a test for read-only properties, not supported by ProxyManager https://github.com/FriendsOfPHP/proxy-manager-lts/issues/26 --- src/Hydrator/HydratorFactory.php | 17 ++----- .../ObjectCastPropertyAccessor.php | 2 +- tests/Documents/Tag.php | 2 +- .../Functional/ReadOnlyPropertiesTest.php | 51 +++++++++++++++++++ .../Mapping/LegacyReflectionFieldsTest.php | 27 ++++++++-- 5 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 tests/Tests/Functional/ReadOnlyPropertiesTest.php diff --git a/src/Hydrator/HydratorFactory.php b/src/Hydrator/HydratorFactory.php index 5a821677fb..37a8644cee 100644 --- a/src/Hydrator/HydratorFactory.php +++ b/src/Hydrator/HydratorFactory.php @@ -451,29 +451,18 @@ public function hydrate(object $document, array $data, array $hints = []): array } } + // Skip initialization to not load any object data if (PHP_VERSION_ID >= 80400) { $metadata->reflClass->markLazyObjectAsInitialized($document); } if ($document instanceof InternalProxy) { - // Skip initialization to not load any object data $document->__setInitialized(true); } // Support for legacy proxy-manager-lts - if ($document instanceof GhostObjectInterface && $document->getProxyInitializer() !== null) { - // Inject an empty initialiser to not load any object data - $document->setProxyInitializer(static function ( - GhostObjectInterface $ghostObject, - string $method, // we don't care - array $parameters, // we don't care - &$initializer, - array $properties, // we currently do not use this - ): bool { - $initializer = null; - - return true; - }); + if ($document instanceof GhostObjectInterface) { + $document->setProxyInitializer(null); } $data = $this->getHydratorFor($metadata->name)->hydrate($document, $data, $hints); diff --git a/src/Mapping/PropertyAccessors/ObjectCastPropertyAccessor.php b/src/Mapping/PropertyAccessors/ObjectCastPropertyAccessor.php index dced04aed5..34ac25cd26 100644 --- a/src/Mapping/PropertyAccessors/ObjectCastPropertyAccessor.php +++ b/src/Mapping/PropertyAccessors/ObjectCastPropertyAccessor.php @@ -52,7 +52,7 @@ public function setValue(object $object, mixed $value): void $object->__setInitialized(false); } elseif ($object instanceof GhostObjectInterface && ! $object->isProxyInitialized()) { $initializer = $object->getProxyInitializer(); - $object->setProxyInitializer(); + $object->setProxyInitializer(null); $this->reflectionProperty->setValue($object, $value); $object->setProxyInitializer($initializer); } else { diff --git a/tests/Documents/Tag.php b/tests/Documents/Tag.php index 5461d1d7ea..70471bb678 100644 --- a/tests/Documents/Tag.php +++ b/tests/Documents/Tag.php @@ -14,7 +14,7 @@ class Tag public ?string $id; #[ODM\Field] - public readonly string $name; + public string $name; /** @var Collection */ #[ODM\ReferenceMany(targetDocument: BlogPost::class, mappedBy: 'tags')] diff --git a/tests/Tests/Functional/ReadOnlyPropertiesTest.php b/tests/Tests/Functional/ReadOnlyPropertiesTest.php new file mode 100644 index 0000000000..ff0dfd28d8 --- /dev/null +++ b/tests/Tests/Functional/ReadOnlyPropertiesTest.php @@ -0,0 +1,51 @@ +dm->getConfiguration(); + if (! $configuration->isNativeLazyObjectEnabled() && ! $configuration->isLazyGhostObjectEnabled()) { + $this->markTestSkipped('Read-only properties are not supported by the legacy Proxy Manager. https://github.com/FriendsOfPHP/proxy-manager-lts/issues/26'); + } + + $document = new ReadOnlyProperties('Test Name'); + $document->onlyRead = new ReadOnlyProperties('Nested Name'); + $this->dm->persist($document); + $this->dm->persist($document->onlyRead); + $this->dm->flush(); + $this->dm->clear(); + + $document = $this->dm->getRepository(ReadOnlyProperties::class)->find($document->id); + $this->assertEquals('Test Name', $document->name); + $this->assertEquals('Nested Name', $document->onlyRead->name); + } +} + +#[Document] +class ReadOnlyProperties +{ + #[Id] + public readonly string $id; // @phpstan-ignore property.uninitializedReadonly (initialized by reflection) + + #[Field] + public readonly string $name; + + #[ReferenceOne(targetDocument: self::class)] + public ?self $onlyRead; + + public function __construct(string $name) + { + $this->name = $name; + } +} diff --git a/tests/Tests/Mapping/LegacyReflectionFieldsTest.php b/tests/Tests/Mapping/LegacyReflectionFieldsTest.php index 860f953130..9d8c201f12 100644 --- a/tests/Tests/Mapping/LegacyReflectionFieldsTest.php +++ b/tests/Tests/Mapping/LegacyReflectionFieldsTest.php @@ -4,14 +4,18 @@ namespace Doctrine\ODM\MongoDB\Tests\Mapping; +use Doctrine\ODM\MongoDB\Mapping\Annotations\Document; +use Doctrine\ODM\MongoDB\Mapping\Annotations\Field; +use Doctrine\ODM\MongoDB\Mapping\Annotations\Id; use Doctrine\ODM\MongoDB\Mapping\LegacyReflectionFields; use Doctrine\ODM\MongoDB\Tests\BaseTestCase; use Documents\Address; -use Documents\Tag; use Documents\User; use LogicException; use PHPUnit\Framework\Attributes\IgnoreDeprecations; +use function sprintf; + #[IgnoreDeprecations] class LegacyReflectionFieldsTest extends BaseTestCase { @@ -56,20 +60,33 @@ public function testGetSet(): void public function testGetSetReadonly(): void { - $class = $this->dm->getClassMetadata(Tag::class); + $class = $this->dm->getClassMetadata(ReadOnlyProperty::class); self::assertInstanceOf(LegacyReflectionFields::class, $class->reflFields); - $tag = new Tag('Important'); + $tag = new ReadOnlyProperty('Important'); $this->dm->persist($tag); $this->dm->flush(); - $tag = $this->dm->find(Tag::class, $tag->id); + $tag = $this->dm->find(ReadOnlyProperty::class, $tag->id); // Accessing the readonly property through reflection self::assertEquals('Important', $class->getReflectionProperty('name')->getValue($tag)); self::expectException(LogicException::class); - self::expectExceptionMessage('Attempting to change readonly property Documents\Tag::$name'); + self::expectExceptionMessage(sprintf('Attempting to change readonly property %s::$name', ReadOnlyProperty::class)); $class->getReflectionProperty('name')->setValue($tag, 'Very Important'); } } + +#[Document] +class ReadOnlyProperty +{ + #[Id] + public string $id; + + public function __construct( + #[Field] + public readonly string $name, + ) { + } +}