PHP readonly properties and inheritance cause error on hydration #7042

Open
opened 2026-01-22 15:43:33 +01:00 by admin · 0 comments
Owner

Originally created by @chapterjason on GitHub (Sep 21, 2022).

Bug Report

Q A
BC Break no
Version 2.13.1

Summary

Using php readonly properties and inheritance (changing scope) causes an error while trying to set the property in the hydration process.

Current behavior

Error from the doctrine reproducer
PHP Fatal error:  Uncaught Error: Cannot initialize readonly property Chapterjason\DoctrineReadonlyReproducer\ValueObject\AggregatedRootId::$value from scope Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId in /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php:59
Stack trace:
#0 /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php(59): ReflectionProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...')
#1 /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultReflectionPropertyBase.php(64): Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...')
#2 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php(40): Doctrine\Persistence\Reflection\TypedNoDefaultRuntimePublicReflectionProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...')
#3 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php(81): Doctrine\ORM\Mapping\ReflectionReadonlyProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...')
#4 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php(2753): Doctrine\ORM\Mapping\ReflectionEmbeddedProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\Entity\Book), 'agri-632b6cb768...')
#5 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(156): Doctrine\ORM\UnitOfWork->createEntity('Chapterjason\\Do...', Array, Array)
#6 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(63): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateRowData(Array, Array)
#7 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php(270): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateAllData()
#8 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php(758): Doctrine\ORM\Internal\Hydration\AbstractHydrator->hydrateAll(Object(Doctrine\DBAL\Result), Object(Doctrine\ORM\Query\ResultSetMapping), Array)
#9 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php(768): Doctrine\ORM\Persisters\Entity\BasicEntityPersister->load(Array, NULL)
#10 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php(521): Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadById(Array)
#11 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php(199): Doctrine\ORM\EntityManager->find('Chapterjason\\Do...', Array, NULL, NULL)
#12 /[...]/doctrine-readonly-reproducer/index.php(34): Doctrine\ORM\EntityRepository->find('agri-632b6cb768...')
#13 {main}
  thrown in /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php on line 59

How to reproduce

Simplified reproducer https://onlinephp.io/c/57532:

<?php

abstract class AggregateRootId implements Stringable
{
    public readonly string $value;

    final public function __construct(?string $value = null)
    {
        $this->value = $value ?? "test";
    }

    public function __toString(): string
    {
        return $this->value;
    }
}

final class BookId extends AggregateRootId
{
}

$ref = new ReflectionClass(BookId::class);
$instance = $ref->newInstanceWithoutConstructor();
$prop = $ref->getProperty('value');
$prop->setValue($instance, 'new-id');

I also created a reproducer with doctrine https://github.com/chapterjason/doctrine-readonly-reproducer but if you want I can make a reproducer with a pull request.

Expected behavior

The readonly property will be hydrated as expected.

Originally created by @chapterjason on GitHub (Sep 21, 2022). ### Bug Report | Q | A |------------ | ------ | BC Break | no | Version | 2.13.1 #### Summary Using php readonly properties and inheritance (changing scope) causes an error while trying to set the property in the hydration process. #### Current behavior <details> <summary>Error from the doctrine reproducer</summary> ``` PHP Fatal error: Uncaught Error: Cannot initialize readonly property Chapterjason\DoctrineReadonlyReproducer\ValueObject\AggregatedRootId::$value from scope Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId in /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php:59 Stack trace: #0 /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php(59): ReflectionProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...') #1 /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/TypedNoDefaultReflectionPropertyBase.php(64): Doctrine\Persistence\Reflection\RuntimePublicReflectionProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...') #2 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php(40): Doctrine\Persistence\Reflection\TypedNoDefaultRuntimePublicReflectionProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...') #3 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ReflectionEmbeddedProperty.php(81): Doctrine\ORM\Mapping\ReflectionReadonlyProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\ValueObject\BookId), 'agri-632b6cb768...') #4 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php(2753): Doctrine\ORM\Mapping\ReflectionEmbeddedProperty->setValue(Object(Chapterjason\DoctrineReadonlyReproducer\Entity\Book), 'agri-632b6cb768...') #5 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(156): Doctrine\ORM\UnitOfWork->createEntity('Chapterjason\\Do...', Array, Array) #6 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php(63): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateRowData(Array, Array) #7 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php(270): Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator->hydrateAllData() #8 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php(758): Doctrine\ORM\Internal\Hydration\AbstractHydrator->hydrateAll(Object(Doctrine\DBAL\Result), Object(Doctrine\ORM\Query\ResultSetMapping), Array) #9 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php(768): Doctrine\ORM\Persisters\Entity\BasicEntityPersister->load(Array, NULL) #10 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php(521): Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadById(Array) #11 /[...]/doctrine-readonly-reproducer/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php(199): Doctrine\ORM\EntityManager->find('Chapterjason\\Do...', Array, NULL, NULL) #12 /[...]/doctrine-readonly-reproducer/index.php(34): Doctrine\ORM\EntityRepository->find('agri-632b6cb768...') #13 {main} thrown in /[...]/doctrine-readonly-reproducer/vendor/doctrine/persistence/src/Persistence/Reflection/RuntimePublicReflectionProperty.php on line 59 ``` </details> #### How to reproduce Simplified reproducer [https://onlinephp.io/c/57532](https://onlinephp.io/c/57532): ```php <?php abstract class AggregateRootId implements Stringable { public readonly string $value; final public function __construct(?string $value = null) { $this->value = $value ?? "test"; } public function __toString(): string { return $this->value; } } final class BookId extends AggregateRootId { } $ref = new ReflectionClass(BookId::class); $instance = $ref->newInstanceWithoutConstructor(); $prop = $ref->getProperty('value'); $prop->setValue($instance, 'new-id'); ``` I also created a reproducer with doctrine [https://github.com/chapterjason/doctrine-readonly-reproducer](https://github.com/chapterjason/doctrine-readonly-reproducer) but if you want I can make a reproducer with a pull request. #### Expected behavior The readonly property will be hydrated as expected.
admin added the Bug label 2026-01-22 15:43:33 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7042