Skip to content

Commit a2d510c

Browse files
stofnicolas-grekas
andcommitted
Fix the initialization of lazy-ghost proxies with postLoad listeners
PostLoad listeners might initialize values for transient properties, so the proxy should not skip initialization when using transient properties. Co-authored-by: Nicolas Grekas <[email protected]>
1 parent 2a4ebca commit a2d510c

File tree

6 files changed

+159
-2
lines changed

6 files changed

+159
-2
lines changed

src/Proxy/ProxyFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersi
378378
$class = $entityPersister->getClassMetadata();
379379

380380
foreach ($class->getReflectionProperties() as $property) {
381-
if (isset($identifier[$property->name]) || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
381+
if (isset($identifier[$property->name])) {
382382
continue;
383383
}
384384

@@ -448,7 +448,7 @@ private function getProxyFactory(string $className): Closure
448448
foreach ($reflector->getProperties($filter) as $property) {
449449
$name = $property->name;
450450

451-
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
451+
if ($property->isStatic() || ! isset($identifiers[$name])) {
452452
continue;
453453
}
454454

src/UnitOfWork.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use Exception;
5050
use InvalidArgumentException;
5151
use RuntimeException;
52+
use Symfony\Component\VarExporter\Hydrator;
5253
use UnexpectedValueException;
5354

5455
use function array_chunk;
@@ -2944,6 +2945,11 @@ public function createEntity($className, array $data, &$hints = [])
29442945

29452946
if ($this->isUninitializedObject($entity)) {
29462947
$entity->__setInitialized(true);
2948+
2949+
if ($this->em->getConfiguration()->isLazyGhostObjectEnabled()) {
2950+
// Initialize properties that have default values to their default value (similar to what
2951+
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
2952+
}
29472953
} else {
29482954
if (
29492955
! isset($hints[Query::HINT_REFRESH])
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\GH11524;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
9+
/**
10+
* @ORM\Entity
11+
* @ORM\Table(name="gh11524_entities")
12+
*/
13+
class GH11524Entity
14+
{
15+
/**
16+
* @ORM\Id
17+
* @ORM\Column(type="integer")
18+
* @ORM\GeneratedValue
19+
*
20+
* @var int|null
21+
*/
22+
public $id = null;
23+
24+
/**
25+
* @ORM\ManyToOne(targetEntity="GH11524Relation")
26+
* @ORM\JoinColumn(name="relation_id", referencedColumnName="id", nullable=true)
27+
*
28+
* @var GH11524Relation|null
29+
*/
30+
public $relation = null;
31+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\GH11524;
6+
7+
use Doctrine\ORM\Event\PostLoadEventArgs;
8+
9+
class GH11524Listener
10+
{
11+
public function postLoad(PostloadEventArgs $eventArgs): void
12+
{
13+
$object = $eventArgs->getObject();
14+
15+
if (! $object instanceof GH11524Relation) {
16+
return;
17+
}
18+
19+
$object->setCurrentLocale('en');
20+
}
21+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\Models\GH11524;
6+
7+
use Doctrine\ORM\Mapping as ORM;
8+
use LogicException;
9+
10+
/**
11+
* @ORM\Entity
12+
* @ORM\Table(name="gh11524_relations")
13+
*/
14+
class GH11524Relation
15+
{
16+
/**
17+
* @ORM\Id
18+
* @ORM\Column(type="integer")
19+
* @ORM\GeneratedValue
20+
*
21+
* @var int|null
22+
*/
23+
public $id;
24+
25+
/**
26+
* @ORM\Column(type="string")
27+
*
28+
* @var string
29+
*/
30+
public $name;
31+
32+
/**
33+
* @var string|null
34+
*/
35+
private $currentLocale;
36+
37+
public function setCurrentLocale(string $locale): void
38+
{
39+
$this->currentLocale = $locale;
40+
}
41+
42+
public function getTranslation(): string
43+
{
44+
if ($this->currentLocale === null) {
45+
throw new LogicException('The current locale must be set to retrieve translation.');
46+
}
47+
48+
return 'fake';
49+
}
50+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\ORM\Events;
8+
use Doctrine\Tests\Models\GH11524\GH11524Entity;
9+
use Doctrine\Tests\Models\GH11524\GH11524Listener;
10+
use Doctrine\Tests\Models\GH11524\GH11524Relation;
11+
use Doctrine\Tests\OrmFunctionalTestCase;
12+
13+
class GH11524Test extends OrmFunctionalTestCase
14+
{
15+
protected function setUp(): void
16+
{
17+
parent::setUp();
18+
19+
$this->createSchemaForModels(
20+
GH11524Entity::class,
21+
GH11524Relation::class
22+
);
23+
24+
$this->_em->getEventManager()->addEventListener(Events::postLoad, new GH11524Listener());
25+
}
26+
27+
public function testPostLoadCalledOnProxy(): void
28+
{
29+
$relation = new GH11524Relation();
30+
$relation->name = 'test';
31+
$this->_em->persist($relation);
32+
33+
$entity = new GH11524Entity();
34+
$entity->relation = $relation;
35+
36+
$this->_em->persist($entity);
37+
$this->_em->flush();
38+
39+
$this->_em->clear();
40+
41+
$reloadedEntity = $this->_em->find(GH11524Entity::class, $entity->id);
42+
43+
$reloadedRelation = $reloadedEntity->relation;
44+
45+
$this->assertTrue($this->isUninitializedObject($reloadedRelation));
46+
47+
$this->assertSame('fake', $reloadedRelation->getTranslation(), 'The property set by the postLoad listener must get initialized on usage.');
48+
}
49+
}

0 commit comments

Comments
 (0)