Skip to content
This repository

initial support for DocumentManager::move() #103

Merged
merged 8 commits into from about 2 years ago

3 participants

Lukas Kahwe Smith Christophe Coevoet David Buchmann
Lukas Kahwe Smith
Collaborator

see http://www.doctrine-project.org/jira/browse/PHPCR-48

This was surprisingly easy to implement. Open question for me is if its matters when exactly we execute the move (right now its done after everything else) and if its ok to skip the move if the node is no longer registered.

Christophe Coevoet

I think the behavior should match the behavior of the ORM: it you call persist() on an entity scheduled for removal in the UoW, it is removed from the scheduled deletions. the same should apply here: moving a node should cancel a previous deletion in the UoW, and deleting a node in the UoW should cancel its scheduled moves. This way, the second point in your comment is solved: there is no possible inconsistency as it cannot be moved an deleted at the same time

Lukas Kahwe Smith
Collaborator

ok .. good point.

Lukas Kahwe Smith
Collaborator

ok .. it seems we didn't handle this properly for deletes atm. so i fixed that and added support for this in move.

Lukas Kahwe Smith lsmith77 merged commit d2fbfdb into from
Lukas Kahwe Smith lsmith77 closed this
David Buchmann
Collaborator

so any @Parent @Child(ren) annotated fields do not get updated by the move operation, right? we should document that, could be confusing.

Lukas Kahwe Smith
Collaborator

correct.

David Buchmann dbu referenced this pull request from a commit
David Buchmann adding some doc for move operation #103 9556fc7
David Buchmann
Collaborator

actually the @Parent can not go wrong as the moved document is still refreshed, and any other document does not change its parent, but the @Id field of children will be wrong too

Lukas Kahwe Smith
Collaborator

yes .. i guess we could update the id's of children if they are connected to the parent by a mapping. for others it would be tricky .. however move operations are not common. so we could do the additional effort of updating id's when we do have move operations on all managed documents.

David Buchmann
Collaborator

agreed. i just made that last comment for completeness if anybody looks at this pull request again. the doc says to call clear after the move, and thats good enough for me for now. tracking that stuff inside jackalope was enough of a pain, no need to re-start in the odm again...

Lukas Kahwe Smith
Collaborator

i guess if we had observation .. we could hook in there ..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
12  lib/Doctrine/ODM/PHPCR/DocumentManager.php
@@ -482,6 +482,18 @@ public function getLocalesFor($document)
482 482
     }
483 483
 
484 484
     /**
  485
+     * Move the previously persisted document and all its children in the tree
  486
+     *
  487
+     * @param object $object
  488
+     * @param string $targetPath
  489
+     */
  490
+    public function move($object, $targetPath)
  491
+    {
  492
+        $this->errorIfClosed();
  493
+        $this->unitOfWork->scheduleMove($object, $targetPath);
  494
+    }
  495
+
  496
+    /**
485 497
      * Remove the previously persisted document and all its children from the tree
486 498
      *
487 499
      * Be aware of the PHPCR tree structure: this removes all nodes with a path under
88  lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -54,6 +54,7 @@ class UnitOfWork
54 54
     const STATE_MANAGED = 2;
55 55
     const STATE_REMOVED = 3;
56 56
     const STATE_DETACHED = 4;
  57
+    const STATE_MOVED = 5;
57 58
 
58 59
     /**
59 60
      * @var DocumentManager
@@ -145,6 +146,11 @@ class UnitOfWork
145 146
     /**
146 147
      * @var array
147 148
      */
  149
+    private $scheduledMoves = array();
  150
+
  151
+    /**
  152
+     * @var array
  153
+     */
148 154
     private $scheduledRemovals = array();
149 155
 
150 156
     /**
@@ -494,10 +500,10 @@ private function doScheduleInsert($document, &$visited, $overrideIdGenerator = n
494 500
         $visited[$oid] = true;
495 501
 
496 502
         $class = $this->dm->getClassMetadata(get_class($document));
497  
-        $state = $this->getDocumentState($document);
498 503
 
499 504
         $this->cascadeScheduleParentInsert($class, $document, $visited);
500 505
 
  506
+        $state = $this->getDocumentState($document);
501 507
         switch ($state) {
502 508
             case self::STATE_NEW:
503 509
                 $this->persistNew($class, $document, $overrideIdGenerator);
@@ -505,8 +511,11 @@ private function doScheduleInsert($document, &$visited, $overrideIdGenerator = n
505 511
             case self::STATE_MANAGED:
506 512
                 // TODO: Change Tracking Deferred Explicit
507 513
                 break;
  514
+            case self::STATE_MOVED:
  515
+                unset($this->scheduledMoves[$oid]);
  516
+                $this->documentState[$oid] = self::STATE_MANAGED;
  517
+                break;
508 518
             case self::STATE_REMOVED:
509  
-                // document becomes managed again
510 519
                 unset($this->scheduledRemovals[$oid]);
511 520
                 $this->documentState[$oid] = self::STATE_MANAGED;
512 521
                 break;
@@ -600,10 +609,45 @@ private function getIdGenerator($type)
600 609
         return $this->idGenerators[$type];
601 610
     }
602 611
 
  612
+    public function scheduleMove($document, $targetPath)
  613
+    {
  614
+        $oid = spl_object_hash($document);
  615
+        $this->scheduledMoves[$oid] = array($document, $targetPath);
  616
+
  617
+        $state = $this->getDocumentState($document);
  618
+        switch ($state) {
  619
+            case self::STATE_NEW:
  620
+                unset($this->scheduledInserts[$oid]);
  621
+                break;
  622
+            case self::STATE_REMOVED:
  623
+                unset($this->scheduledRemovals[$oid]);
  624
+                break;
  625
+            case self::STATE_DETACHED:
  626
+                throw new \InvalidArgumentException('Detached document passed to move(): '.self::objToStr($document));
  627
+                break;
  628
+        }
  629
+
  630
+        $this->documentState[$oid] = self::STATE_MOVED;
  631
+    }
  632
+
603 633
     public function scheduleRemove($document)
604 634
     {
605 635
         $oid = spl_object_hash($document);
606 636
         $this->scheduledRemovals[$oid] = $document;
  637
+
  638
+        $state = $this->getDocumentState($document);
  639
+        switch ($state) {
  640
+            case self::STATE_NEW:
  641
+                unset($this->scheduledInserts[$oid]);
  642
+                break;
  643
+            case self::STATE_MOVED:
  644
+                unset($this->scheduledMoves[$oid]);
  645
+                break;
  646
+            case self::STATE_DETACHED:
  647
+                throw new \InvalidArgumentException('Detached document passed to remove(): '.self::objToStr($document));
  648
+                break;
  649
+        }
  650
+
607 651
         $this->documentState[$oid] = self::STATE_REMOVED;
608 652
 
609 653
         $class = $this->dm->getClassMetadata(get_class($document));
@@ -631,6 +675,7 @@ private function purgeChildren($document)
631 675
                 $oid = spl_object_hash($child);
632 676
                 unset(
633 677
                     $this->scheduledRemovals[$oid],
  678
+                    $this->scheduledMoves[$oid],
634 679
                     $this->scheduledInserts[$oid],
635 680
                     $this->scheduledUpdates[$oid],
636 681
                     $this->scheduledAssociationUpdates[$oid]
@@ -759,7 +804,7 @@ public function computeChangeSet(ClassMetadata $class, $document)
759 804
                 && isset($actualData[$class->nodename])
760 805
                 && $this->originalData[$oid][$class->nodename] !== $actualData[$class->nodename]
761 806
             ) {
762  
-                throw new PHPCRException('The Nodename property is immutable ('.$this->originalData[$oid][$class->nodename].' !== '.$actualData[$class->nodename].'). Please use PHPCR\Session::move to rename the document: '.self::objToStr($document));
  807
+                throw new PHPCRException('The Nodename property is immutable ('.$this->originalData[$oid][$class->nodename].' !== '.$actualData[$class->nodename].'). Please use DocumentManager::move to rename the document: '.self::objToStr($document));
763 808
             }
764 809
             if (isset($this->originalData[$oid][$class->parentMapping])
765 810
                 && isset($actualData[$class->parentMapping])
@@ -771,7 +816,7 @@ public function computeChangeSet(ClassMetadata $class, $document)
771 816
                 && isset($actualData[$class->identifier])
772 817
                 && $this->originalData[$oid][$class->identifier] !== $actualData[$class->identifier]
773 818
             ) {
774  
-                throw new PHPCRException('The Id is immutable ('.$this->originalData[$oid][$class->identifier].' !== '.$actualData[$class->identifier].'). Please use PHPCR\Session::move to move the document: '.self::objToStr($document));
  819
+                throw new PHPCRException('The Id is immutable ('.$this->originalData[$oid][$class->identifier].' !== '.$actualData[$class->identifier].'). Please use DocumentManager::move to move the document: '.self::objToStr($document));
775 820
             }
776 821
 
777 822
             // Document is "fully" MANAGED: it was already fully persisted before
@@ -834,7 +879,7 @@ public function computeChangeSet(ClassMetadata $class, $document)
834 879
         foreach ($class->childMappings as $name => $childMapping) {
835 880
             if ($actualData[$name]) {
836 881
                 if ($this->originalData[$oid][$name] && $this->originalData[$oid][$name] !== $actualData[$name]) {
837  
-                    throw new PHPCRException('Cannot move/copy children by assignment as it would be ambiguous. Please use the PHPCR\Session::move() or PHPCR\Session::copy() operations for this: '.self::objToStr($document));
  882
+                    throw new PHPCRException('Cannot move/copy children by assignment as it would be ambiguous. Please use the DocumentManager::move() or PHPCR\Session::copy() operations for this: '.self::objToStr($document));
838 883
                 }
839 884
                 $this->computeChildChanges($childMapping, $actualData[$name], $id);
840 885
             }
@@ -1085,6 +1130,8 @@ public function commit($document = null)
1085 1130
 
1086 1131
             $this->executeRemovals($this->scheduledRemovals);
1087 1132
 
  1133
+            $this->executeMoves($this->scheduledMoves);
  1134
+
1088 1135
             $this->session->save();
1089 1136
 
1090 1137
             if ($utx) {
@@ -1115,6 +1162,7 @@ public function commit($document = null)
1115 1162
         $this->scheduledUpdates =
1116 1163
         $this->scheduledAssociationUpdates =
1117 1164
         $this->scheduledRemovals =
  1165
+        $this->scheduledMoves =
1118 1166
         $this->scheduledInserts =
1119 1167
         $this->visitedCollections = array();
1120 1168
     }
@@ -1305,7 +1353,7 @@ private function executeUpdates($documents, $dispatchEvents = true)
1305 1353
                             $child->remove();
1306 1354
                         }
1307 1355
                     } elseif ($this->originalData[$oid][$fieldName] && $this->originalData[$oid][$fieldName] !== $fieldValue) {
1308  
-                        throw new PHPCRException('Cannot move/copy children by assignment as it would be ambiguous. Please use the PHPCR\Session::move() or PHPCR\Session::copy() operations for this.');
  1356
+                        throw new PHPCRException('Cannot move/copy children by assignment as it would be ambiguous. Please use the DocumentManager::move() or PHPCR\Session::copy() operations for this.');
1309 1357
                     }
1310 1358
                 }
1311 1359
             }
@@ -1324,6 +1372,30 @@ private function executeUpdates($documents, $dispatchEvents = true)
1324 1372
     }
1325 1373
 
1326 1374
     /**
  1375
+     * Executes all document moves
  1376
+     *
  1377
+     * @param array $documents array of all to be moved documents
  1378
+     */
  1379
+    private function executeMoves($documents)
  1380
+    {
  1381
+        foreach ($documents as $oid => $value) {
  1382
+            list($document, $targetPath) = $value;
  1383
+            if (!isset($this->nodesMap[$oid])) {
  1384
+                continue;
  1385
+            }
  1386
+
  1387
+            $class = $this->dm->getClassMetadata(get_class($document));
  1388
+            $path = $class->getIdentifierValue($document);
  1389
+            $this->session->move($path, $targetPath);
  1390
+            if ($targetPath !== $this->nodesMap[$oid]->getPath()) {
  1391
+                throw new \RuntimeException("Move failed to move from '$path' to '$targetPath' for document: ".self::objToStr($document));
  1392
+            }
  1393
+            $class->setIdentifierValue($document, $targetPath);
  1394
+            $this->originalData[$oid][$class->identifier] = $targetPath;
  1395
+        }
  1396
+    }
  1397
+
  1398
+    /**
1327 1399
      * Executes all document removals
1328 1400
      *
1329 1401
      * @param array $documents array of all to be removed documents
@@ -1331,11 +1403,11 @@ private function executeUpdates($documents, $dispatchEvents = true)
1331 1403
     private function executeRemovals($documents)
1332 1404
     {
1333 1405
         foreach ($documents as $oid => $document) {
1334  
-            $class = $this->dm->getClassMetadata(get_class($document));
1335 1406
             if (!isset($this->nodesMap[$oid])) {
1336 1407
                 continue;
1337 1408
             }
1338 1409
 
  1410
+            $class = $this->dm->getClassMetadata(get_class($document));
1339 1411
             $this->doRemoveAllTranslations($document, $class);
1340 1412
 
1341 1413
             $this->nodesMap[$oid]->remove();
@@ -1351,7 +1423,6 @@ private function executeRemovals($documents)
1351 1423
         }
1352 1424
     }
1353 1425
 
1354  
-
1355 1426
     /**
1356 1427
      * @see DocumentManager::findVersionByName
1357 1428
      */
@@ -1701,6 +1772,7 @@ public function clear()
1701 1772
         $this->scheduledUpdates =
1702 1773
         $this->scheduledAssociationUpdates =
1703 1774
         $this->scheduledInserts =
  1775
+        $this->scheduledMoves =
1704 1776
         $this->scheduledRemovals =
1705 1777
         $this->visitedCollections =
1706 1778
         $this->documentHistory =
164  tests/Doctrine/Tests/ODM/PHPCR/Functional/BasicCrudTest.php
@@ -111,14 +111,86 @@ public function testMultivaluePropertyWithOnlyOneValueUpdatedToMultiValue()
111 111
         $this->assertEquals($userNew->numbers->toArray(), $userNew2->numbers->toArray());
112 112
     }
113 113
 
114  
-    public function testDelete()
  114
+    public function testMoveWithClear()
115 115
     {
116 116
         $this->dm->clear();
117 117
         $user = $this->dm->find($this->type, '/functional/user');
118 118
         $this->assertNotNull($user, 'User must exist');
119 119
 
  120
+        $this->dm->move($user, '/functional/user2');
  121
+        $user = $this->dm->find($this->type, '/functional/user');
  122
+        $this->assertNotNull($user, 'User must exist');
  123
+        $this->dm->flush();
  124
+        $this->dm->clear();
  125
+
  126
+        $user = $this->dm->find($this->type, '/functional/user2');
  127
+        $this->assertNotNull($user, 'User must exist');
  128
+    }
  129
+
  130
+    public function testMove()
  131
+    {
  132
+        $this->dm->clear();
  133
+        $user = $this->dm->find($this->type, '/functional/user');
  134
+        $this->assertNotNull($user, 'User must exist');
  135
+
  136
+        $this->dm->move($user, '/functional/user2');
  137
+        $this->dm->flush();
  138
+
  139
+        $user = $this->dm->find($this->type, '/functional/user2');
  140
+        $this->assertNotNull($user, 'User must exist');
  141
+    }
  142
+
  143
+    public function testMoveWithPersist()
  144
+    {
  145
+        $this->dm->clear();
  146
+        $user = $this->dm->find($this->type, '/functional/user');
  147
+        $this->assertNotNull($user, 'User must exist');
  148
+
  149
+        $this->dm->move($user, '/functional/user2');
  150
+        $this->dm->persist($user);
  151
+        $this->dm->flush();
  152
+
  153
+        $user = $this->dm->find($this->type, '/functional/user');
  154
+        $this->assertNotNull($user, 'User must exist');
  155
+    }
  156
+
  157
+    public function testMoveWithRemove()
  158
+    {
  159
+        $this->dm->clear();
  160
+        $user = $this->dm->find($this->type, '/functional/user');
  161
+        $this->assertNotNull($user, 'User must exist');
  162
+
  163
+        $this->dm->move($user, '/functional/user2');
120 164
         $this->dm->remove($user);
121 165
         $this->dm->flush();
  166
+
  167
+        $user = $this->dm->find($this->type, '/functional/user');
  168
+        $this->assertNull($user, 'User must be null after deletion');
  169
+        $user = $this->dm->find($this->type, '/functional/user2');
  170
+        $this->assertNull($user, 'User must be null after deletion');
  171
+    }
  172
+
  173
+    public function testMoveNoFlush()
  174
+    {
  175
+        $this->dm->clear();
  176
+        $user = $this->dm->find($this->type, '/functional/user');
  177
+        $this->assertNotNull($user, 'User must exist');
  178
+
  179
+        $this->dm->move($user, '/functional/user2');
  180
+        $user = $this->dm->find($this->type, '/functional/user');
  181
+        $this->assertNotNull($user, 'User must exist');
  182
+    }
  183
+
  184
+    public function testRemoveWithClear()
  185
+    {
  186
+        $this->dm->clear();
  187
+        $user = $this->dm->find($this->type, '/functional/user');
  188
+        $this->assertNotNull($user, 'User must exist');
  189
+
  190
+        $this->dm->remove($user);
  191
+        $user = $this->dm->find($this->type, '/functional/user');
  192
+        $this->assertNotNull($user, 'User must exist');
  193
+        $this->dm->flush();
122 194
         $this->dm->clear();
123 195
 
124 196
         $user = $this->dm->find($this->type, '/functional/user');
@@ -138,6 +210,47 @@ public function testRemove()
138 210
         $this->assertNull($user, 'User must be null after deletion');
139 211
     }
140 212
 
  213
+    public function testRemoveWithMove()
  214
+    {
  215
+        $this->dm->clear();
  216
+        $user = $this->dm->find($this->type, '/functional/user');
  217
+        $this->assertNotNull($user, 'User must exist');
  218
+
  219
+        $this->dm->remove($user);
  220
+        $this->dm->move($user, '/functional/user2');
  221
+        $this->dm->flush();
  222
+
  223
+        $user = $this->dm->find($this->type, '/functional/user');
  224
+        $this->assertNull($user, 'User must be null after deletion');
  225
+        $user = $this->dm->find($this->type, '/functional/user2');
  226
+        $this->assertNotNull($user, 'User must exist');
  227
+    }
  228
+
  229
+    public function testRemoveWithPersist()
  230
+    {
  231
+        $this->dm->clear();
  232
+        $user = $this->dm->find($this->type, '/functional/user');
  233
+        $this->assertNotNull($user, 'User must exist');
  234
+
  235
+        $this->dm->remove($user);
  236
+        $this->dm->persist($user);
  237
+        $this->dm->flush();
  238
+
  239
+        $user = $this->dm->find($this->type, '/functional/user');
  240
+        $this->assertNotNull($user, 'User must exist');
  241
+    }
  242
+
  243
+    public function testRemoveNoFlush()
  244
+    {
  245
+        $this->dm->clear();
  246
+        $user = $this->dm->find($this->type, '/functional/user');
  247
+        $this->assertNotNull($user, 'User must exist');
  248
+
  249
+        $this->dm->remove($user);
  250
+        $user = $this->dm->find($this->type, '/functional/user');
  251
+        $this->assertNotNull($user, 'User must exist');
  252
+    }
  253
+
141 254
     public function testRemoveAndInsertAfterFlush()
142 255
     {
143 256
         $this->dm->clear();
@@ -438,6 +551,55 @@ public function testFlushSingleDocumentWithParent()
438 551
         $user3 = $this->dm->find('Doctrine\Tests\ODM\PHPCR\Functional\User', '/functional/test/team/team');
439 552
         $this->assertEquals('changed', $user3->username);
440 553
     }
  554
+
  555
+    public function testDetach()
  556
+    {
  557
+        $user = $this->dm->find($this->type, '/functional/user');
  558
+        $user->username = "new-name";
  559
+
  560
+        $this->dm->detach($user);
  561
+        $this->dm->flush();
  562
+        $this->dm->clear();
  563
+
  564
+        $newUser = $this->dm->find($this->type, '/functional/user');
  565
+        $this->assertEquals('lsmith', $newUser->username);
  566
+    }
  567
+
  568
+    /**
  569
+     * @expectedException \InvalidArgumentException
  570
+     */
  571
+    public function testDetachWithPerist()
  572
+    {
  573
+        $user = $this->dm->find($this->type, '/functional/user');
  574
+        $user->username = "new-name";
  575
+
  576
+        $this->dm->detach($user);
  577
+        $this->dm->persist($user);
  578
+    }
  579
+
  580
+    /**
  581
+     * @expectedException \InvalidArgumentException
  582
+     */
  583
+    public function testDetachWithMove()
  584
+    {
  585
+        $user = $this->dm->find($this->type, '/functional/user');
  586
+        $user->username = "new-name";
  587
+
  588
+        $this->dm->detach($user);
  589
+        $this->dm->move($user, '/functional/user2');
  590
+    }
  591
+
  592
+    /**
  593
+     * @expectedException \InvalidArgumentException
  594
+     */
  595
+    public function testDetachWithRemove()
  596
+    {
  597
+        $user = $this->dm->find($this->type, '/functional/user');
  598
+        $user->username = "new-name";
  599
+
  600
+        $this->dm->detach($user);
  601
+        $this->dm->remove($user);
  602
+    }
441 603
 }
442 604
 
443 605
 /**
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.