From aead77d597e0d902f6512e14a38192490fb8195f Mon Sep 17 00:00:00 2001 From: Ivan Strygin <feolius@gmail.com> Date: Fri, 24 Dec 2021 02:10:42 +0300 Subject: [PATCH] Put actual value instead of index inside $originalEntityData. (#9244) This fixes a bug with redundant UPDATE queries, that are executed when some entity uses foreign index of other entity as a primary key. This happens when after inserting related entities with $em->flush() call, you do the second $em->flush() without changing any data inside entities. Fixes GH8217. Co-authored-by: ivan <ivan.strygin@managinglife.com> --- lib/Doctrine/ORM/UnitOfWork.php | 8 +- .../ORM/Functional/Ticket/GH8217Test.php | 108 ++++++++++++++++++ 2 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/GH8217Test.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 37a199cfbab..18541b5681c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1166,14 +1166,16 @@ private function addToEntityIdentifiersAndEntityMap( $identifier = []; foreach ($class->getIdentifierFieldNames() as $idField) { - $value = $class->getFieldValue($entity, $idField); + $origValue = $class->getFieldValue($entity, $idField); + $value = null; if (isset($class->associationMappings[$idField])) { // NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced. - $value = $this->getSingleIdentifierValue($value); + $value = $this->getSingleIdentifierValue($origValue); } - $identifier[$idField] = $this->originalEntityData[$oid][$idField] = $value; + $identifier[$idField] = $value ?? $origValue; + $this->originalEntityData[$oid][$idField] = $origValue; } $this->entityStates[$oid] = self::STATE_MANAGED; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH8217Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH8217Test.php new file mode 100644 index 00000000000..8b46448e15e --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH8217Test.php @@ -0,0 +1,108 @@ +<?php + +declare(strict_types=1); + +namespace Doctrine\Tests\ORM\Functional\Ticket; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\GeneratedValue; +use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\JoinColumn; +use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\OneToMany; +use Doctrine\Tests\OrmFunctionalTestCase; + +use function count; + +final class GH8217Test extends OrmFunctionalTestCase +{ + protected function setUp(): void + { + parent::setUp(); + $this->setUpEntitySchema( + [ + GH8217Collection::class, + GH8217CollectionItem::class, + ] + ); + } + + /** + * @group GH-8217 + */ + public function testNoQueriesAfterSecondFlush(): void + { + $collection = new GH8217Collection(); + $collection->addItem(new GH8217CollectionItem($collection, 0)); + $collection->addItem(new GH8217CollectionItem($collection, 1)); + $this->_em->persist($collection); + $this->_em->flush(); + + $logger = $this->_sqlLoggerStack; + $queriesNumberBeforeSecondFlush = count($logger->queries); + $this->_em->flush(); + $queriesNumberAfterSecondFlush = count($logger->queries); + self::assertEquals($queriesNumberBeforeSecondFlush, $queriesNumberAfterSecondFlush); + } +} + +/** + * @Entity + */ +class GH8217Collection +{ + /** + * @var int + * @Id + * @Column(type="integer") + * @GeneratedValue + */ + public $id; + + /** + * @psalm-var Collection<int, GH8217CollectionItem> + * @OneToMany(targetEntity="GH8217CollectionItem", mappedBy="collection", + * cascade={"persist", "remove"}, orphanRemoval=true) + */ + public $items; + + public function __construct() + { + $this->items = new ArrayCollection(); + } + + public function addItem(GH8217CollectionItem $item): void + { + $this->items->add($item); + } +} + +/** + * @Entity + */ +class GH8217CollectionItem +{ + /** + * @var GH8217Collection + * @Id + * @ManyToOne(targetEntity="GH8217Collection", inversedBy="items") + * @JoinColumn(name="id", referencedColumnName="id") + */ + public $collection; + + /** + * @var int + * @Id + * @Column(type="integer", options={"unsigned": true}) + */ + public $collectionIndex; + + public function __construct(GH8217Collection $collection, int $collectionIndex) + { + $this->collection = $collection; + $this->collectionIndex = $collectionIndex; + } +}