From d44e6e1a9ec833c4380bf46d26907c3e3e11a22a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Fri, 11 Aug 2017 15:13:39 +0200 Subject: [PATCH] #6613 #6614 ensuring that only newly added items that weren't loaded are restored in the dirty state of the collection --- lib/Doctrine/ORM/PersistentCollection.php | 36 +++++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 2210c3c31..3dc254a53 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -692,23 +692,41 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec protected function doInitialize() { // Has NEW objects added through add(). Remember them. - $newObjects = []; + $newlyAddedDirtyObjects = []; if ($this->isDirty) { - $newObjects = $this->collection->toArray(); + $newlyAddedDirtyObjects = $this->collection->toArray(); } $this->collection->clear(); $this->em->getUnitOfWork()->loadCollection($this); $this->takeSnapshot(); - // Reattach NEW objects added through add(), if any. - if ($newObjects) { - foreach ($newObjects as $obj) { - $this->collection->add($obj); - } - - $this->isDirty = true; + if ($newlyAddedDirtyObjects) { + $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects); } } + + /** + * @param object[] $newObjects + * + * @return void + * + * Note: the only reason why this entire looping/complexity is performed via `spl_object_hash` + * is because we want to prevent using `array_udiff()`, which is likely to cause very + * high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in + * core, which is faster than using a callback for comparisons + */ + private function restoreNewObjectsInDirtyCollection(array $newObjects) + { + $loadedObjects = $this->collection->toArray(); + $newObjectsByOid = array_combine(array_map('spl_object_hash', $newObjects), $newObjects); + $loadedObjectsByOid = array_combine(array_map('spl_object_hash', $loadedObjects), $loadedObjects); + $newObjectsThatWereNotLoaded = array_diff_key($newObjectsByOid, $loadedObjectsByOid); + + // Reattach NEW objects added through add(), if any. + array_walk($newObjectsThatWereNotLoaded, [$this->collection, 'add']); + + $this->isDirty = true; + } }