Skip to content

Commit

Permalink
Make it possible to have non-NULLable self-referencing associations w…
Browse files Browse the repository at this point in the history
…hen using application-provided IDs

The change makes the `BasicEntityPersister` not schedule an extra update in the case of an entity referencing itself and having an application-provided ID (the "NONE" generator strategy).

While it looks like a special corner case, the INSERTion of a NULL value plus the extra update require the self-referencing column to be defined as NULLable. This is only an issue for the self-referencing entity case. All other associations work naturally.

Fixes #7877, closes #7882.

Co-authored-by: Sylvain Fabre <[email protected]>
  • Loading branch information
mpdude and sylfabre committed Jun 1, 2023
1 parent da0998c commit b0d8fec
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 1 deletion.
12 changes: 11 additions & 1 deletion lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,17 @@ protected function prepareUpdateData($entity, bool $isInsert = false)
if ($newVal !== null) {
$oid = spl_object_id($newVal);

if (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
if ($newVal === $entity && $this->class->isIdentifierNatural()) {
// When the associated entity is in fact the entity itself (= we have a
// self-referencing entity), and the ID has already been provided by the
// application, then we can INSERT all the data right away and need not
// schedule the extra update. Due to the "references itself" condition,
// this looks like a very special case. But, in fact, this is the
// corner case that makes it possible to define such self-referencing
// columns as not-NULLable when using application-side ID generators.

// do nothing
} elseif (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.
Expand Down
60 changes: 60 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/GH7877Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional;

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

use function uniqid;

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

$this->createSchemaForModels(
GH7877ApplicationGeneratedIdEntity::class
);
}

public function testSelfReferenceWithApplicationGeneratedIdMayBeNotNullable(): void
{
$entity = new GH7877ApplicationGeneratedIdEntity();
$entity->id = uniqid();
$entity->parent = $entity;

$this->expectNotToPerformAssertions();

$this->_em->persist($entity);
$this->_em->flush();
}
}

/**
* @ORM\Entity
*/
class GH7877ApplicationGeneratedIdEntity
{
/**
* @ORM\Id
* @ORM\Column(type="string")
* @ORM\GeneratedValue(strategy="NONE")
*
* @var string
*/
public $id;

/**
* @ORM\ManyToOne(targetEntity="GH7877ApplicationGeneratedIdEntity")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=false)
*
* @var self
*/
public $parent;
}

0 comments on commit b0d8fec

Please sign in to comment.