Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
sylfabre committed May 30, 2023
1 parent 8fba9d6 commit a41cae0
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 11 deletions.
54 changes: 43 additions & 11 deletions lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -680,17 +680,12 @@ protected function prepareUpdateData($entity, bool $isInsert = false)
continue;
}

if ($newVal !== null) {
$oid = spl_object_id($newVal);

if (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
// The associated entity $newVal is not yet persisted, so we must
// set $newVal = null, in order to insert a null value and schedule an
// extra update on the UnitOfWork.
$uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]);

$newVal = null;
}
if ($this->isExtraUpdateRequired($entity, $newVal)) {
// The associated entity $newVal is not yet persisted, so we must
// set $newVal = null, in order to insert a null value and schedule an
// extra update on the UnitOfWork.
$uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]);
$newVal = null;
}

$newValId = null;
Expand Down Expand Up @@ -718,6 +713,43 @@ protected function prepareUpdateData($entity, bool $isInsert = false)
return $result;
}

/**
* Decides if an extra update is required for the entity being persisted
* This is only required if the associated entity is different from the current one,
* and if the current entity relies on the database to generate its id
*
* @param object $entity
* @param object|null $newVal
*
* @return bool Returns true is an extra update is required
*/
private function isExtraUpdateRequired($entity, $newVal): bool
{
if ($newVal === null) {
return false;
}

$oid = spl_object_id($newVal);
$uow = $this->em->getUnitOfWork();
if (! (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal))) {
return false;
}

if ($newVal !== $entity) {
return true;
}

$identifiers = $this->class->getIdentifier();
// Only single-column identifiers are supported
$entityChangeSet = $uow->getEntityChangeSet($entity);
if (count($identifiers) === 1 && isset($entityChangeSet[$identifiers[0]])) {
// Extra update is required if the current entity does not have yet a value for its identifier
return $entityChangeSet[$identifiers[0]][1] === null;
}

return true;
}

/**
* Prepares the data changeset of a managed entity for database insertion (initial INSERT).
* The changeset of the entity is obtained from the currently running UnitOfWork.
Expand Down
152 changes: 152 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/GH7877Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;

/**
* @group GH7877
*/
class GH7877Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$classMetadatas = [
$this->_em->getClassMetadata(GH7877ApplicationGenerated::class),
$this->_em->getClassMetadata(GH7877DatabaseGenerated::class),
];
// We first drop the schema to avoid collision between tests
$this->_schemaTool->dropSchema($classMetadatas);
$this->_schemaTool->createSchema($classMetadatas);
}

public function providerDifferentEntity(): iterable
{
yield [GH7877ApplicationGenerated::class];
yield [GH7877DatabaseGenerated::class];
}

/**
* @dataProvider providerDifferentEntity
*/
public function testExtraUpdateWithDifferentEntities(string $class): void
{
$parent = new $class($parentId = 1);
$this->_em->persist($parent);

$child = new $class($childId = 2);
$child->parent = $parent;
$this->_em->persist($child);

if ($this->isQueryLogAvailable()) {
$this->getQueryLog()->reset();
}

$this->_em->flush();
if ($this->isQueryLogAvailable()) {
self::assertQueryCount(5);
}

$this->_em->clear();

$child = $this->_em->find($class, $childId);
$this->assertSame($parentId, $child->parent->id);
}

public function testNoExtraUpdateWithApplicationGeneratedId(): void
{
$entity = new GH7877ApplicationGenerated($entityId = 1);
$entity->parent = $entity;
$this->_em->persist($entity);

if ($this->isQueryLogAvailable()) {
$this->getQueryLog()->reset();
}

$this->_em->flush();
if ($this->isQueryLogAvailable()) {
self::assertQueryCount(5);
}

$this->_em->clear();

$child = $this->_em->find(GH7877ApplicationGenerated::class, $entityId);
$this->assertSame($entityId, $child->parent->id);
}

public function textExtraUpdateWithDatabaseGeneratedId(): void
{
$entity = new GH7877DatabaseGenerated();
$entity->parent = $entity;
$this->_em->persist($entity);

if ($this->isQueryLogAvailable()) {
$this->getQueryLog()->reset();
}

$this->_em->flush();
if ($this->isQueryLogAvailable()) {
self::assertQueryCount(4);
}

$entityId = $entity->id;
$this->_em->clear();

$child = $this->_em->find(GH7877DatabaseGenerated::class, $entityId);
$this->assertSame($entityId, $child->parent->id);
}
}

/**
* @ORM\Entity
*/
class GH7877ApplicationGenerated
{
public function __construct(int $id)
{
$this->id = $id;
}

/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="NONE")
*
* @var int
*/
public $id;

/**
* @ORM\ManyToOne(targetEntity="Doctrine\Tests\ORM\Functional\GH7877ApplicationGenerated")
*
* @var self
*/
public $parent;
}

/**
* @ORM\Entity
*/
class GH7877DatabaseGenerated
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*
* @var int
*/
public $id;

/**
* @ORM\ManyToOne(targetEntity="Doctrine\Tests\ORM\Functional\GH7877DatabaseGenerated")
*
* @var self
*/
public $parent;
}

0 comments on commit a41cae0

Please sign in to comment.