Skip to content
This repository

Bugfix for MODM166 - collections not handled properly in recomputeSingleDocumentChangeSet #336

Merged
merged 1 commit into from almost 2 years ago

2 participants

Tim Roediger Jeremy Mikola
Tim Roediger

Added test.

Added fix.

Did modest refactoring in uow to remove code duplication between computeChangeSet and recomputeSingleDocumentChangeSet.

Jeremy Mikola jmikola commented on the diff June 25, 2012
lib/Doctrine/ODM/MongoDB/UnitOfWork.php
((10 lines not shown))
664 721
 
665 722
             foreach ($actualData as $propName => $actualValue) {
  723
+                // skip not saved fields
  724
+                if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
  725
+                    continue;
  726
+                }
2
Jeremy Mikola Owner
jmikola added a note June 25, 2012

I noticed this is also (and originally) further down in the method's $class->fieldMappings as $mapping loop. Was it a bug that the field also wasn't skipped in this loop?

@jmikola I noticed that not saved fields weren't being skipped as early as possible, so I moved it up to make execution a little faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/ODM/MongoDB/UnitOfWork.php
... ...
@@ -736,47 +802,6 @@ public function computeChangeSet(ClassMetadata $class, $document)
736 802
     }
737 803
 
738 804
     /**
739  
-     * Computes all the changes that have been done to documents and collections
740  
-     * since the last commit and stores these changes in the _documentChangeSet map
741  
-     * temporarily for access by the persisters, until the UoW commit is finished.
742  
-     */
743  
-    public function computeChangeSets()
2
Jeremy Mikola Owner
jmikola added a note June 25, 2012

It doesn't look like this method was changed (only relocated). Assuming there were no changes, can you move it back to keep the diff smaller? It makes it a bit more difficult to review the actual changes in this PR.

@jmikola I moved it to try and put the methods in a logical order, but I can move it back to make the PR easier to read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM166Test.php
((45 lines not shown))
  45
+            $phonenumbers[] = $phonenumber->getPhonenumber();
  46
+        }
  47
+        sort($phonenumbers);
  48
+
  49
+        $this->assertEquals(array('1111', '2222'), $phonenumbers);
  50
+    }
  51
+}
  52
+
  53
+class MODM166EventListener
  54
+{
  55
+    public function onFlush(OnFlushEventArgs $eventArgs)
  56
+    {
  57
+        $documentManager = $eventArgs->getDocumentManager();
  58
+        $unitOfWork = $documentManager->getUnitOfWork();
  59
+
  60
+        foreach ($unitOfWork->getScheduledDocumentUpdates() AS $document) {
1
Jeremy Mikola Owner
jmikola added a note June 25, 2012

CS: lower-case AS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM166Test.php
((51 lines not shown))
  51
+}
  52
+
  53
+class MODM166EventListener
  54
+{
  55
+    public function onFlush(OnFlushEventArgs $eventArgs)
  56
+    {
  57
+        $documentManager = $eventArgs->getDocumentManager();
  58
+        $unitOfWork = $documentManager->getUnitOfWork();
  59
+
  60
+        foreach ($unitOfWork->getScheduledDocumentUpdates() AS $document) {
  61
+            $metadata = $documentManager->getClassMetadata(get_class($document));
  62
+            $document->addPhonenumber(new Phonenumber('2222'));
  63
+            $unitOfWork->recomputeSingleDocumentChangeSet($metadata, $document);
  64
+        }
  65
+    }
  66
+}
1
Jeremy Mikola Owner
jmikola added a note June 25, 2012

CS: Newline at end of file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM166Test.php
... ...
@@ -0,0 +1,66 @@
  1
+<?php
  2
+
  3
+namespace Doctrine\ODM\MongoDB\Tests\Functional\Ticket;
  4
+
  5
+use Doctrine\ODM\MongoDB\Events;
  6
+use Documents\User;
  7
+use Documents\Phonenumber;
  8
+use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs;
1
Jeremy Mikola Owner
jmikola added a note June 25, 2012

CS: alphabetical order for use statements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM166Test.php
((7 lines not shown))
  7
+use Documents\Phonenumber;
  8
+use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs;
  9
+
  10
+class MODM166Test extends \Doctrine\ODM\MongoDB\Tests\BaseTest
  11
+{
  12
+
  13
+    public function setUp()
  14
+    {
  15
+        parent::setUp();
  16
+
  17
+        $this->listener = new MODM166EventListener();
  18
+        $evm = $this->dm->getEventManager();
  19
+        $events = array(
  20
+            Events::onFlush
  21
+        );
  22
+        $evm->addEventListener($events, $this->listener);
1
Jeremy Mikola Owner
jmikola added a note June 25, 2012

Let's replace these four lines with: $evm->addEventListener(Events::onFlush, $this->listener); to be concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jeremy Mikola
Owner

@superdweebie: It looks like we might be stepping on each others' toes with #329. I'd like to get @jwage to review that first and merge down, so it may be a good idea to hold off on these changes until that's done.

Jeremy Mikola
Owner

@superdweebie: Evidently there's no conflict. Feel free to make any revisions.

Jeremy Mikola jmikola commented on the diff June 25, 2012
lib/Doctrine/ODM/MongoDB/UnitOfWork.php
((28 lines not shown))
896  
-                        $this->collectionDeletions[] = $orgValue;
897  
-                        $this->collectionUpdates[] = $actualValue;
898  
-                        $this->visitedCollections[] = $actualValue;
899  
-                    }
900  
-                }
901  
-            } else if ($orgValue !== $actualValue) {
902  
-                $changeSet[$propName] = array($orgValue, $actualValue);
903  
-            }
904  
-        }
905  
-        if ($changeSet) {
906  
-            if (isset($this->documentChangeSets[$oid])) {
907  
-                $this->documentChangeSets[$oid] = $changeSet + $this->documentChangeSets[$oid];
908  
-            }
909  
-            $this->originalDocumentData[$oid] = $actualData;
910  
-        }
  897
+        $this->computeOrRecomputeChangeSet($class, $document, true);
2
Jeremy Mikola Owner
jmikola added a note June 25, 2012

Is it a problem if recomputeSingleDocumentChangeSet() never had the "Look for changes in associations of the document" loop that was in computeChangeSet() and is now in computeOrRecomputeChangeSet()? That is the only difference in execution path that I can see.

@jmikola Yes, this is the fix for the bug. If recomputeSingleDocumentChangeSet() doesn't have the extra loop, then changes to associations aren't processed properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Tim Roediger

@jmikola After stuffing around with git (and learning a few new things), I think I have all your requested changes in the commit.

Jeremy Mikola jmikola commented on the diff June 26, 2012
lib/Doctrine/ODM/MongoDB/UnitOfWork.php
@@ -895,7 +920,7 @@ private function computeAssociationChanges($parentDocument, $mapping, $value)
895 920
      * @param object $document The document for which to (re)calculate the change set.
896 921
      * @throws InvalidArgumentException If the passed document is not MANAGED.
897 922
      */
898  
-    public function recomputeSingleDocumentChangeSet($class, $document)
  923
+    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, $document)
1
Jeremy Mikola Owner
jmikola added a note June 26, 2012

@jwage: Was there any particular reason many of the UoW methods didn't type-hint for ClassMetadata? Is it a micro-optimization?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jeremy Mikola
Owner

Thanks @superdweebie; the diff is much more concise now :)

@jwage: I left one question for you above. Beyond that, this PR has my blessing so feel free to merge down if it looks alright to you.

Jeremy Mikola
Owner

@superdweebie: I was just re-testing this and noticed that your test case (MODM166Test) currently passes in the master branch without this commit merged. If that's the case, is there really a bug being fixed in this commit or is it just refactoring?

Based on the diff, the only functional change (not refactoring) that I can see is skipping NotSaved fields in the first foreach loop of computeChangeSet() -- and that's certainly unrelated to the MODM-166.

Tim Roediger

@jmikola Are you sure? I just loaded up the latest (commit e2c1fbf), and test for MODM166 was failing against it.

Jeremy Mikola
Owner

My mistake, I must have been on the wrong branch when I tested that.

Jeremy Mikola jmikola merged commit 11ddc65 into from June 27, 2012
Jeremy Mikola jmikola closed this June 27, 2012
Tim Roediger

@jmikola :) No problem

Jeremy Mikola
Owner

I checked ORM, and they also have type-hinting in the changeset compute methods (but not elsewhere). That was the only outstanding question, so we're merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Jun 26, 2012
Tim Roediger Added test and fix for bug MODM166
 with CS cleanup
e22a0e2
This page is out of date. Refresh to see the latest.
73  lib/Doctrine/ODM/MongoDB/UnitOfWork.php
@@ -643,6 +643,18 @@ public function computeChangeSet(ClassMetadata $class, $document)
643 643
             $class->invokeLifecycleCallbacks(Events::preFlush, $document);
644 644
         }
645 645
 
  646
+        $this->computeOrRecomputeChangeSet($class, $document);
  647
+    }
  648
+
  649
+    /**
  650
+     * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet
  651
+     *
  652
+     * @param \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class
  653
+     * @param object $document
  654
+     * @param boolean $recompute
  655
+     */
  656
+    private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
  657
+    {
646 658
         $oid = spl_object_hash($document);
647 659
         $actualData = $this->getDocumentActualData($document);
648 660
         $isNewDocument = ! isset($this->originalDocumentData[$oid]);
@@ -660,9 +672,18 @@ public function computeChangeSet(ClassMetadata $class, $document)
660 672
             // and we have a copy of the original data
661 673
             $originalData = $this->originalDocumentData[$oid];
662 674
             $isChangeTrackingNotify = $class->isChangeTrackingNotify();
663  
-            $changeSet = $isChangeTrackingNotify ? $this->documentChangeSets[$oid] : array();
  675
+            if ($isChangeTrackingNotify && !$recompute) {
  676
+                $changeSet = $this->documentChangeSets[$oid];
  677
+            } else {
  678
+                $changeSet = array();
  679
+            }
664 680
 
665 681
             foreach ($actualData as $propName => $actualValue) {
  682
+                // skip not saved fields
  683
+                if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
  684
+                    continue;
  685
+                }
  686
+
666 687
                 $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
667 688
 
668 689
                 // skip if value has not changed
@@ -732,7 +753,11 @@ public function computeChangeSet(ClassMetadata $class, $document)
732 753
                 $changeSet[$propName] = array($orgValue, $actualValue);
733 754
             }
734 755
             if ($changeSet) {
735  
-                $this->documentChangeSets[$oid] = $changeSet;
  756
+                if ($recompute) {
  757
+                    $this->documentChangeSets[$oid] = $changeSet + $this->documentChangeSets[$oid];
  758
+                } else {
  759
+                    $this->documentChangeSets[$oid] = $changeSet;
  760
+                }
736 761
                 $this->originalDocumentData[$oid] = $actualData;
737 762
                 $this->documentUpdates[$oid] = $document;
738 763
             }
@@ -895,7 +920,7 @@ private function computeAssociationChanges($parentDocument, $mapping, $value)
895 920
      * @param object $document The document for which to (re)calculate the change set.
896 921
      * @throws InvalidArgumentException If the passed document is not MANAGED.
897 922
      */
898  
-    public function recomputeSingleDocumentChangeSet($class, $document)
  923
+    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, $document)
899 924
     {
900 925
         $oid = spl_object_hash($document);
901 926
 
@@ -907,45 +932,7 @@ public function recomputeSingleDocumentChangeSet($class, $document)
907 932
             $class = $this->dm->getClassMetadata(get_class($document));
908 933
         }
909 934
 
910  
-        $actualData = $this->getDocumentActualData($document);
911  
-        $isChangeTrackingNotify = $class->isChangeTrackingNotify();
912  
-
913  
-        $originalData = isset($this->originalDocumentData[$oid]) ? $this->originalDocumentData[$oid] : array();
914  
-        $changeSet = array();
915  
-        foreach ($actualData as $propName => $actualValue) {
916  
-            // skip not saved fields
917  
-            if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
918  
-                continue;
919  
-            }
920  
-            $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
921  
-            if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one' && $orgValue !== $actualValue) {
922  
-                if ($orgValue !== null) {
923  
-                    $this->scheduleOrphanRemoval($orgValue);
924  
-                }
925  
-                $changeSet[$propName] = array($orgValue, $actualValue);
926  
-            } else if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $orgValue !== $actualValue) {
927  
-                $changeSet[$propName] = array($orgValue, $actualValue);
928  
-            } else if ($isChangeTrackingNotify) {
929  
-                continue;
930  
-            } else if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
931  
-                if ($orgValue !== $actualValue) {
932  
-                    $changeSet[$propName] = array($orgValue, $actualValue);
933  
-                    if ($orgValue instanceof PersistentCollection) {
934  
-                        $this->collectionDeletions[] = $orgValue;
935  
-                        $this->collectionUpdates[] = $actualValue;
936  
-                        $this->visitedCollections[] = $actualValue;
937  
-                    }
938  
-                }
939  
-            } else if ($orgValue !== $actualValue) {
940  
-                $changeSet[$propName] = array($orgValue, $actualValue);
941  
-            }
942  
-        }
943  
-        if ($changeSet) {
944  
-            if (isset($this->documentChangeSets[$oid])) {
945  
-                $this->documentChangeSets[$oid] = $changeSet + $this->documentChangeSets[$oid];
946  
-            }
947  
-            $this->originalDocumentData[$oid] = $actualData;
948  
-        }
  935
+        $this->computeOrRecomputeChangeSet($class, $document, true);
949 936
     }
950 937
 
951 938
     private function persistNew($class, $document)
@@ -1656,7 +1643,7 @@ public function tryGetById($id, $rootClassName)
1656 1643
         return isset($this->identityMap[$rootClassName][$id]) ?
1657 1644
                 $this->identityMap[$rootClassName][$id] : false;
1658 1645
     }
1659  
-    
  1646
+
1660 1647
     /**
1661 1648
      * Schedules a document for dirty-checking at commit-time.
1662 1649
      *
63  tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/MODM166Test.php
... ...
@@ -0,0 +1,63 @@
  1
+<?php
  2
+
  3
+namespace Doctrine\ODM\MongoDB\Tests\Functional\Ticket;
  4
+
  5
+use Doctrine\ODM\MongoDB\Event\OnFlushEventArgs;
  6
+use Doctrine\ODM\MongoDB\Events;
  7
+use Documents\Phonenumber;
  8
+use Documents\User;
  9
+
  10
+class MODM166Test extends \Doctrine\ODM\MongoDB\Tests\BaseTest
  11
+{
  12
+
  13
+    public function setUp()
  14
+    {
  15
+        parent::setUp();
  16
+
  17
+        $this->listener = new MODM166EventListener();
  18
+        $evm = $this->dm->getEventManager();
  19
+        $evm->addEventListener(Events::onFlush, $this->listener);
  20
+        return $this->dm;
  21
+    }
  22
+
  23
+    public function testUpdateCollectionDuringOnFlushAndRecomputSingleDocumentChangeSet()
  24
+    {
  25
+        // create a test document
  26
+        $test = new User();
  27
+        $test->setUsername('toby');
  28
+        $test->addPhonenumber(new Phonenumber('1111'));
  29
+
  30
+        $this->dm->persist($test);
  31
+        $this->dm->flush();
  32
+
  33
+        $test->setUsername('lucy');
  34
+        $this->dm->flush();
  35
+        $this->dm->clear();
  36
+
  37
+        $repository = $this->dm->getRepository(get_class($test));
  38
+        $test = $repository->findOneBy(array('username' => 'lucy'));
  39
+
  40
+        $phonenumbers = array();
  41
+        foreach ($test->getPhonenumbers() as $phonenumber){
  42
+            $phonenumbers[] = $phonenumber->getPhonenumber();
  43
+        }
  44
+        sort($phonenumbers);
  45
+
  46
+        $this->assertEquals(array('1111', '2222'), $phonenumbers);
  47
+    }
  48
+}
  49
+
  50
+class MODM166EventListener
  51
+{
  52
+    public function onFlush(OnFlushEventArgs $eventArgs)
  53
+    {
  54
+        $documentManager = $eventArgs->getDocumentManager();
  55
+        $unitOfWork = $documentManager->getUnitOfWork();
  56
+
  57
+        foreach ($unitOfWork->getScheduledDocumentUpdates() as $document) {
  58
+            $metadata = $documentManager->getClassMetadata(get_class($document));
  59
+            $document->addPhonenumber(new Phonenumber('2222'));
  60
+            $unitOfWork->recomputeSingleDocumentChangeSet($metadata, $document);
  61
+        }
  62
+    }
  63
+}
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.