Skip to content

Commit abc6a40

Browse files
committed
fix: calling scheduleForInsert twice
If scheduleForInsert was called in prePersist hook already, then persistNew need to check this case first, otherwise a ORMInvalidArgumentException will be thrown
1 parent 158605b commit abc6a40

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

src/UnitOfWork.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,9 @@ private function persistNew(ClassMetadata $class, $entity): void
10591059

10601060
$this->entityStates[$oid] = self::STATE_MANAGED;
10611061

1062-
$this->scheduleForInsert($entity);
1062+
if (!isset($this->entityInsertions[$oid])) {
1063+
$this->scheduleForInsert($entity);;
1064+
}
10631065
}
10641066

10651067
/** @param mixed[] $idValue */
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional;
6+
7+
use Doctrine\ORM\Event\PrePersistEventArgs;
8+
use Doctrine\ORM\Events;
9+
use Doctrine\ORM\Mapping\Column;
10+
use Doctrine\ORM\Mapping\Entity;
11+
use Doctrine\ORM\Mapping\GeneratedValue;
12+
use Doctrine\ORM\Mapping\Id;
13+
use Doctrine\ORM\Mapping\ManyToOne;
14+
use Doctrine\Tests\OrmFunctionalTestCase;
15+
16+
/**
17+
* PrePersistEventTest
18+
*/
19+
class PrePersistEventTest extends OrmFunctionalTestCase
20+
{
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->createSchemaForModels(
26+
EntityWithUnmapEntity::class,
27+
EntityWithCascadeAssociation::class,
28+
);
29+
}
30+
31+
public function testCallingPersistInPrePersistHook(): void
32+
{
33+
$entityWithUnmapped = new EntityWithUnmapEntity();
34+
$entityWithCascade = new EntityWithCascadeAssociation();
35+
36+
$entityWithUnmapped->unmapped = $entityWithCascade;
37+
$entityWithCascade->cascaded = $entityWithUnmapped;
38+
39+
$this->_em->getEventManager()->addEventListener(Events::prePersist, new PrePersistUnmappedPersistListener);
40+
$this->_em->persist($entityWithUnmapped);
41+
42+
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithCascade));
43+
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithUnmapped));
44+
}
45+
}
46+
47+
class PrePersistUnmappedPersistListener
48+
{
49+
public function prePersist(PrePersistEventArgs $args): void
50+
{
51+
$object = $args->getObject();
52+
53+
if ($object instanceof EntityWithUnmapEntity) {
54+
$uow = $args->getObjectManager()->getUnitOfWork();
55+
56+
if (!$uow->isInIdentityMap($object->unmapped) && !$uow->isScheduledForInsert($object->unmapped) && $object->unmapped) {
57+
$args->getObjectManager()->persist($object->unmapped);
58+
}
59+
}
60+
}
61+
}
62+
63+
#[Entity]
64+
class EntityWithUnmapEntity
65+
{
66+
#[Id]
67+
#[Column(type: 'string', length: 255)]
68+
#[GeneratedValue(strategy: 'NONE')]
69+
public string $id;
70+
71+
public EntityWithCascadeAssociation|null $unmapped = null;
72+
73+
public function __construct()
74+
{
75+
$this->id = uniqid(self::class, true);
76+
}
77+
}
78+
79+
#[Entity]
80+
class EntityWithCascadeAssociation
81+
{
82+
#[Id]
83+
#[Column(type: 'string', length: 255)]
84+
#[GeneratedValue(strategy: 'NONE')]
85+
public string $id;
86+
87+
#[ManyToOne(targetEntity: EntityWithUnmapEntity::class, cascade: ['persist'])]
88+
public EntityWithUnmapEntity|null $cascaded = null;
89+
90+
public function __construct()
91+
{
92+
$this->id = uniqid(self::class, true);
93+
}
94+
}

0 commit comments

Comments
 (0)