-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cannot persist new entity if primary key contains a foreign key and the referenced object is in state new and its id is not assigned #7003
Changes from 5 commits
6d11fc1
d1500e2
716ec1a
ec1edd4
cbab976
6608238
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -940,7 +940,18 @@ private function persistNew($class, $entity) | |
$class->setIdentifierValues($entity, $idValue); | ||
} | ||
|
||
$this->entityIdentifiers[$oid] = $idValue; | ||
// Some identifiers may be foreign keys to new entities. | ||
// In this case, we don't have the value yet and should treat it as if we have a post-insert generator | ||
$hasMissingIdsWhichAreForeignKeys = false; | ||
foreach($idValue as $idField => $idFieldValue) { | ||
if($idFieldValue === null && isset($class->associationMappings[$idField])) { | ||
$hasMissingIdsWhichAreForeignKeys = true; | ||
break; | ||
} | ||
} | ||
if(!$hasMissingIdsWhichAreForeignKeys) { | ||
$this->entityIdentifiers[$oid] = $idValue; | ||
} | ||
} | ||
|
||
$this->entityStates[$oid] = self::STATE_MANAGED; | ||
|
@@ -1033,12 +1044,16 @@ private function executeInserts($class) | |
$persister = $this->getEntityPersister($className); | ||
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist); | ||
|
||
$theseInsertions = []; | ||
|
||
foreach ($this->entityInsertions as $oid => $entity) { | ||
|
||
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { | ||
continue; | ||
} | ||
|
||
$theseInsertions[$oid] = $entity; | ||
|
||
$persister->addInsert($entity); | ||
|
||
unset($this->entityInsertions[$oid]); | ||
|
@@ -1067,6 +1082,34 @@ private function executeInserts($class) | |
|
||
$this->addToIdentityMap($entity); | ||
} | ||
} else { | ||
foreach ($theseInsertions as $oid => $entity) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the commit order calculator is working correctly, then this should simply be pushed down in the execution path, no? |
||
if(!isset($this->entityIdentifiers[$oid])) { | ||
//entity was not added to identity map because some identifiers are foreign keys to new entities. | ||
//add it now | ||
$identifier = []; | ||
$idFields = $class->getIdentifierFieldNames(); | ||
foreach ($idFields as $idField) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This block seems to detect whether an identifier is incomplete. Can it be moved to a |
||
$value = $class->getFieldValue($entity, $idField); | ||
|
||
if (isset($class->associationMappings[$idField])) { | ||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. | ||
$value = $this->getSingleIdentifierValue($value); | ||
} | ||
|
||
$identifier[$idField] = $value; | ||
} | ||
|
||
$this->entityIdentifiers[$oid] = $identifier; | ||
|
||
foreach($identifier as $idField => $idValue) { | ||
$this->originalEntityData[$oid][$idField] = $idValue; | ||
} | ||
|
||
$this->entityStates[$oid] = self::STATE_MANAGED; | ||
$this->addToIdentityMap($entity); | ||
} | ||
} | ||
} | ||
|
||
foreach ($entities as $entity) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
namespace Doctrine\Tests\Models\IdentityThroughForeignKeyTest; | ||
|
||
use Doctrine\Common\Collections\ArrayCollection; | ||
use Doctrine\Common\Collections\Collection; | ||
use Doctrine\ORM\Mapping as ORM; | ||
|
||
/** | ||
* Class Product | ||
* @package Doctrine\Tests\Models\IdentityThroughForeignKeyTest | ||
* @Entity() | ||
* @Table(name="identitythroughforeignkey_product") | ||
*/ | ||
class Product | ||
{ | ||
|
||
/** | ||
* @var integer | ||
* @Id() | ||
* @GeneratedValue(strategy="IDENTITY") | ||
* @Column(name="id") | ||
*/ | ||
public $id; | ||
|
||
/** | ||
* @var Collection|ProductColor[] | ||
* @OneToMany(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductColor", mappedBy="product", cascade={"persist"}) | ||
*/ | ||
public $colors; | ||
|
||
/** | ||
* @var Collection|ProductColor[] | ||
* @OneToMany(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductSize", mappedBy="product", cascade={"persist"}) | ||
*/ | ||
public $sizes; | ||
|
||
/** | ||
* @var Collection|ProductVariant[] | ||
* @OneToMany(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductVariant", mappedBy="product", cascade={"persist"}) | ||
*/ | ||
public $variants; | ||
|
||
public function __construct() | ||
{ | ||
$this->colors = new ArrayCollection(); | ||
$this->sizes = new ArrayCollection(); | ||
$this->variants = new ArrayCollection(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
namespace Doctrine\Tests\Models\IdentityThroughForeignKeyTest; | ||
|
||
/** | ||
* Class ProductColor | ||
* @package Doctrine\Tests\Models\IdentityThroughForeignKeyTest | ||
* @Entity() | ||
* @Table(name="identitythroughforeignkey_product_color") | ||
*/ | ||
class ProductColor | ||
{ | ||
|
||
/** | ||
* @var integer | ||
* @Id() | ||
* @GeneratedValue(strategy="IDENTITY") | ||
* @Column(name="id", type="integer") | ||
*/ | ||
public $id; | ||
|
||
/** | ||
* @var Product | ||
* @ManyToOne(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\Product", inversedBy="colors") | ||
* @JoinColumn(name="product_id", referencedColumnName="id") | ||
*/ | ||
public $product; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
namespace Doctrine\Tests\Models\IdentityThroughForeignKeyTest; | ||
|
||
use Doctrine\ORM\Mapping as ORM; | ||
|
||
/** | ||
* Class ProductSize | ||
* @package Doctrine\Tests\Models\IdentityThroughForeignKeyTest | ||
* @Entity() | ||
* @Table(name="identitythroughforeignkey_product_size") | ||
*/ | ||
class ProductSize | ||
{ | ||
|
||
/** | ||
* @var integer | ||
* @Id() | ||
* @GeneratedValue(strategy="IDENTITY") | ||
* @Column(name="id", type="integer") | ||
*/ | ||
public $id; | ||
|
||
/** | ||
* @var Product | ||
* @ManyToOne(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\Product", inversedBy="sizes") | ||
* @JoinColumn(name="product_id", referencedColumnName="id") | ||
*/ | ||
public $product; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
namespace Doctrine\Tests\Models\IdentityThroughForeignKeyTest; | ||
|
||
/** | ||
* Class ProductVariant | ||
* @package Doctrine\Tests\Models\IdentityThroughForeignKeyTest | ||
* @Entity() | ||
* @Table(name="identitythroughforeignkey_product_variant") | ||
*/ | ||
class ProductVariant | ||
{ | ||
|
||
/** | ||
* @var Product | ||
* @Id() | ||
* @ManyToOne(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\Product", inversedBy="variants") | ||
* @JoinColumn(name="product_id", referencedColumnName="id") | ||
*/ | ||
public $product; | ||
|
||
/** | ||
* @var ProductColor | ||
* @Id() | ||
* @ManyToOne(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductColor") | ||
* @JoinColumn(name="color_id", referencedColumnName="id") | ||
*/ | ||
public $color; | ||
|
||
/** | ||
* @var ProductSize | ||
* @Id() | ||
* @ManyToOne(targetEntity="Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductSize") | ||
* @JoinColumn(name="size_id", referencedColumnName="id") | ||
*/ | ||
public $size; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<?php | ||
|
||
namespace Doctrine\Tests\ORM\Functional; | ||
|
||
use Doctrine\ORM\EntityManager; | ||
use Doctrine\Tests\Mocks\ConnectionMock; | ||
use Doctrine\Tests\Models\IdentityThroughForeignKeyTest\Product; | ||
use Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductColor; | ||
use Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductSize; | ||
use Doctrine\Tests\Models\IdentityThroughForeignKeyTest\ProductVariant; | ||
use Doctrine\Tests\OrmTestCase; | ||
|
||
class IdentityThroughForeignKeyTest extends OrmTestCase | ||
{ | ||
|
||
/** @var EntityManager */ | ||
protected $em; | ||
|
||
protected function setUp() | ||
{ | ||
parent::setUp(); | ||
$this->em = $this->_getTestEntityManager(); | ||
} | ||
|
||
|
||
public function testIdentityThroughForeignKeyCollectionPersistence() | ||
{ | ||
$product = new Product(); | ||
|
||
$color = new ProductColor(); | ||
$color->product = $product; | ||
$product->colors->add($color); | ||
|
||
$size = new ProductSize(); | ||
$size->product = $product; | ||
$product->sizes->add($size); | ||
|
||
$variant = new ProductVariant(); | ||
$variant->product = $product; | ||
$variant->color = $color; | ||
$variant->size = $size; | ||
$product->variants->add($variant); | ||
|
||
$this->em->persist($product); | ||
|
||
//check that flush does not throw | ||
$this->em->flush(); | ||
|
||
$identifier = $this->em->getClassMetadata(ProductVariant::class)->getIdentifierValues($variant); | ||
|
||
foreach ($identifier as $k => $v) { | ||
$identifier[$k] = $this->em->getClassMetadata(get_class($v))->getIdentifierValues($v)['id']; | ||
} | ||
|
||
//check that identityMap content is correct | ||
//find by primary key should return the same instance | ||
$this->assertTrue($variant === $this->em->find(ProductVariant::class, $identifier)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$theseInsertions
is a bad name here