Skip to content

Commit de7eee5

Browse files
authored
Merge pull request #10385 from nicolas-grekas/uninitialized-prop-reproducer
Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors
2 parents 4051937 + 3b8692f commit de7eee5

File tree

5 files changed

+116
-7
lines changed

5 files changed

+116
-7
lines changed

lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php

+4
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ protected function hydrateRowData(array $row, array &$result)
165165
}
166166
}
167167

168+
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
169+
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
170+
}
171+
168172
$uow = $this->_em->getUnitOfWork();
169173
$entity = $uow->createEntity($entityName, $data, $this->_hints);
170174

lib/Doctrine/ORM/Proxy/ProxyFactory.php

+15-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Doctrine\ORM\Utility\IdentifierFlattener;
1919
use Doctrine\Persistence\Mapping\ClassMetadata;
2020
use Doctrine\Persistence\Proxy;
21+
use ReflectionProperty;
2122
use Symfony\Component\VarExporter\ProxyHelper;
2223
use Symfony\Component\VarExporter\VarExporter;
2324

@@ -313,17 +314,24 @@ private function generateSkippedProperties(ClassMetadata $class): string
313314
{
314315
$skippedProperties = ['__isCloning' => true];
315316
$identifiers = array_flip($class->getIdentifierFieldNames());
317+
$filter = ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE;
318+
$reflector = $class->getReflectionClass();
316319

317-
foreach ($class->getReflectionClass()->getProperties() as $property) {
318-
$name = $property->getName();
320+
while ($reflector) {
321+
foreach ($reflector->getProperties($filter) as $property) {
322+
$name = $property->getName();
319323

320-
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
321-
continue;
322-
}
324+
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
325+
continue;
326+
}
323327

324-
$prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : '');
328+
$prefix = $property->isPrivate() ? "\0" . $property->getDeclaringClass()->getName() . "\0" : ($property->isProtected() ? "\0*\0" : '');
329+
330+
$skippedProperties[$prefix . $name] = true;
331+
}
325332

326-
$skippedProperties[$prefix . $name] = true;
333+
$filter = ReflectionProperty::IS_PRIVATE;
334+
$reflector = $reflector->getParentClass();
327335
}
328336

329337
uksort($skippedProperties, 'strnatcmp');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\GH10336;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
/**
10+
* @ORM\Entity
11+
* @ORM\Table(name="gh10336_entities")
12+
*/
13+
class GH10336Entity
14+
{
15+
/**
16+
* @ORM\Id
17+
* @ORM\Column(type="integer")
18+
* @ORM\GeneratedValue
19+
*/
20+
public ?int $id = null;
21+
22+
/**
23+
* @ORM\ManyToOne(targetEntity="GH10336Relation")
24+
* @ORM\JoinColumn(name="relation_id", referencedColumnName="id", nullable=true)
25+
*/
26+
public ?GH10336Relation $relation = null;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\GH10336;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
/**
10+
* @ORM\Entity
11+
* @ORM\Table(name="gh10336_relations")
12+
*/
13+
class GH10336Relation
14+
{
15+
/**
16+
* @ORM\Id
17+
* @ORM\Column(type="integer")
18+
* @ORM\GeneratedValue
19+
*/
20+
public ?int $id = null;
21+
22+
/**
23+
* @ORM\Column(type="string")
24+
*/
25+
public string $value;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\Tests\Models\GH10336\GH10336Entity;
8+
use Doctrine\Tests\Models\GH10336\GH10336Relation;
9+
use Doctrine\Tests\OrmFunctionalTestCase;
10+
11+
/**
12+
* @requires PHP 7.4
13+
*/
14+
final class GH10336Test extends OrmFunctionalTestCase
15+
{
16+
public function setUp(): void
17+
{
18+
parent::setUp();
19+
20+
$this->createSchemaForModels(
21+
GH10336Entity::class,
22+
GH10336Relation::class
23+
);
24+
}
25+
26+
public function testCanAccessRelationPropertyAfterClear(): void
27+
{
28+
$relation = new GH10336Relation();
29+
$relation->value = 'foo';
30+
$entity = new GH10336Entity();
31+
$entity->relation = $relation;
32+
33+
$this->_em->persist($entity);
34+
$this->_em->persist($relation);
35+
$this->_em->flush();
36+
$this->_em->clear();
37+
38+
$entity = $this->_em->find(GH10336Entity::class, 1);
39+
40+
$this->_em->clear();
41+
42+
$this->assertSame('foo', $entity->relation->value);
43+
}
44+
}

0 commit comments

Comments
 (0)