From c9efc1cdee58f23049e42edf776f126090b299e0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 9 Jan 2023 15:57:48 +0100 Subject: [PATCH 1/2] Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors --- .../Hydration/SimpleObjectHydrator.php | 4 ++++ lib/Doctrine/ORM/Proxy/ProxyFactory.php | 23 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index e9063c03660..f6c66b7f3dc 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -165,6 +165,10 @@ protected function hydrateRowData(array $row, array &$result) } } + if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) { + $this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data); + } + $uow = $this->_em->getUnitOfWork(); $entity = $uow->createEntity($entityName, $data, $this->_hints); diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 91a2a89521d..1c98f716e74 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -18,6 +18,7 @@ use Doctrine\ORM\Utility\IdentifierFlattener; use Doctrine\Persistence\Mapping\ClassMetadata; use Doctrine\Persistence\Proxy; +use ReflectionProperty; use Symfony\Component\VarExporter\ProxyHelper; use Symfony\Component\VarExporter\VarExporter; @@ -314,17 +315,23 @@ private function generateSkippedProperties(ClassMetadata $class): string $skippedProperties = ['__isCloning' => true]; $identifiers = array_flip($class->getIdentifierFieldNames()); - foreach ($class->getReflectionClass()->getProperties() as $property) { - $name = $property->getName(); + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); - if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) { - continue; - } + do { + foreach ($reflector->getProperties($filter) as $property) { + $name = $property->getName(); + + if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) { + continue; + } - $prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : ''); + $prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : ''); - $skippedProperties[$prefix . $name] = true; - } + $skippedProperties[$prefix . $name] = true; + } + $filter = ReflectionProperty::IS_PRIVATE; + } while ($reflector = $reflector->getParentClass()); uksort($skippedProperties, 'strnatcmp'); From 3b8692fa4a5be3b9685591c2c42db70e5872afdd Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Thu, 22 Dec 2022 12:46:17 -0500 Subject: [PATCH 2/2] add reproducer --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 13 +++--- .../Tests/Models/GH10336/GH10336Entity.php | 27 ++++++++++++ .../Tests/Models/GH10336/GH10336Relation.php | 26 +++++++++++ .../ORM/Functional/Ticket/GH10336Test.php | 44 +++++++++++++++++++ 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/GH10336/GH10336Entity.php create mode 100644 tests/Doctrine/Tests/Models/GH10336/GH10336Relation.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/GH10336Test.php diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 1c98f716e74..fe19c7d71e1 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -314,11 +314,10 @@ private function generateSkippedProperties(ClassMetadata $class): string { $skippedProperties = ['__isCloning' => true]; $identifiers = array_flip($class->getIdentifierFieldNames()); + $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; + $reflector = $class->getReflectionClass(); - $filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE; - $reflector = $class->getReflectionClass(); - - do { + while ($reflector) { foreach ($reflector->getProperties($filter) as $property) { $name = $property->getName(); @@ -330,8 +329,10 @@ private function generateSkippedProperties(ClassMetadata $class): string $skippedProperties[$prefix . $name] = true; } - $filter = ReflectionProperty::IS_PRIVATE; - } while ($reflector = $reflector->getParentClass()); + + $filter = ReflectionProperty::IS_PRIVATE; + $reflector = $reflector->getParentClass(); + } uksort($skippedProperties, 'strnatcmp'); diff --git a/tests/Doctrine/Tests/Models/GH10336/GH10336Entity.php b/tests/Doctrine/Tests/Models/GH10336/GH10336Entity.php new file mode 100644 index 00000000000..9c4fe9da08b --- /dev/null +++ b/tests/Doctrine/Tests/Models/GH10336/GH10336Entity.php @@ -0,0 +1,27 @@ +createSchemaForModels( + GH10336Entity::class, + GH10336Relation::class + ); + } + + public function testCanAccessRelationPropertyAfterClear(): void + { + $relation = new GH10336Relation(); + $relation->value = 'foo'; + $entity = new GH10336Entity(); + $entity->relation = $relation; + + $this->_em->persist($entity); + $this->_em->persist($relation); + $this->_em->flush(); + $this->_em->clear(); + + $entity = $this->_em->find(GH10336Entity::class, 1); + + $this->_em->clear(); + + $this->assertSame('foo', $entity->relation->value); + } +}