Skip to content

Commit d1fe710

Browse files
authored
Adds adoptChildren method (#19)
1 parent 7760e72 commit d1fe710

File tree

3 files changed

+160
-14
lines changed

3 files changed

+160
-14
lines changed

src/NestedSetInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ public function moveSubTreeBefore(Node $target, Node $node);
168168
*/
169169
public function moveSubTreeAfter(Node $target, Node $node);
170170

171+
/**
172+
* Swaps the parent of a sub-tree to a new parent.
173+
*
174+
* @param \PNX\NestedSet\Node $oldParent
175+
* The old parent.
176+
* @param \PNX\NestedSet\Node $newParent
177+
* The new parent.
178+
*/
179+
public function adoptChildren(Node $oldParent, Node $newParent);
180+
171181
/**
172182
* Gets a node at a specified left position.
173183
*

src/Storage/DbalNestedSet.php

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ protected function insertNodeAtPostion($newLeftPosition, $depth, NodeKey $nodeKe
8585
catch (Exception $e) {
8686
$this->connection->rollBack();
8787
throw $e;
88-
}
89-
finally {
88+
} finally {
9089
$this->connection->setAutoCommit(TRUE);
9190
}
9291
return $newNode;
@@ -259,8 +258,7 @@ public function deleteNode(Node $node) {
259258
catch (Exception $e) {
260259
$this->connection->rollBack();
261260
throw $e;
262-
}
263-
finally {
261+
} finally {
264262
$this->connection->setAutoCommit(TRUE);
265263
}
266264

@@ -296,8 +294,7 @@ public function deleteSubTree(Node $node) {
296294
catch (Exception $e) {
297295
$this->connection->rollBack();
298296
throw $e;
299-
}
300-
finally {
297+
} finally {
301298
$this->connection->setAutoCommit(TRUE);
302299
}
303300

@@ -335,6 +332,15 @@ public function moveSubTreeAfter(Node $target, Node $node) {
335332
$this->moveSubTreeToPosition($newLeftPosition, $node, $target->getDepth());
336333
}
337334

335+
/**
336+
* {@inheritdoc}
337+
*/
338+
public function adoptChildren(Node $oldParent, Node $newParent) {
339+
$children = $this->findChildren($oldParent->getNodeKey());
340+
$newLeftPosition = $newParent->getRight();
341+
$this->moveMultipleSubTreesToPosition($newLeftPosition, $children, $newParent->getDepth() + 1);
342+
}
343+
338344
/**
339345
* Moves a subtree to a new position.
340346
*
@@ -349,17 +355,37 @@ public function moveSubTreeAfter(Node $target, Node $node) {
349355
* If a transaction error occurs.
350356
*/
351357
protected function moveSubTreeToPosition($newLeftPosition, Node $node, $newDepth) {
358+
$this->moveMultipleSubTreesToPosition($newLeftPosition, [$node], $newDepth);
359+
}
360+
361+
/**
362+
* Moves multiple subtrees to a new position.
363+
*
364+
* @param int $newLeftPosition
365+
* The new left position.
366+
* @param \PNX\NestedSet\Node[] $nodes
367+
* The nodes to move.
368+
* @param int $newDepth
369+
* Depth of new position.
370+
*
371+
* @throws \Exception
372+
* If a transaction error occurs.
373+
*/
374+
protected function moveMultipleSubTreesToPosition($newLeftPosition, array $nodes, $newDepth) {
352375
try {
376+
377+
$firstNode = reset($nodes);
378+
$lastNode = end($nodes);
353379
// Calculate position adjustment variables.
354-
$width = $node->getRight() - $node->getLeft() + 1;
355-
$distance = $newLeftPosition - $node->getLeft();
356-
$tempPos = $node->getLeft();
380+
$width = $lastNode->getRight() - $firstNode->getLeft() + 1;
381+
$distance = $newLeftPosition - $firstNode->getLeft();
382+
$tempPos = $firstNode->getLeft();
357383

358384
$this->connection->setAutoCommit(FALSE);
359385
$this->connection->beginTransaction();
360386

361387
// Calculate depth difference.
362-
$depthDiff = $newDepth - $node->getDepth();
388+
$depthDiff = $newDepth - $firstNode->getDepth();
363389

364390
// Backwards movement must account for new space.
365391
if ($distance < 0) {
@@ -383,19 +409,18 @@ protected function moveSubTreeToPosition($newLeftPosition, Node $node, $newDepth
383409

384410
// Remove old space vacated by subtree.
385411
$this->connection->executeUpdate('UPDATE ' . $this->tableName . ' SET left_pos = left_pos - ? WHERE left_pos > ?',
386-
[$width, $node->getRight()]
412+
[$width, $lastNode->getRight()]
387413
);
388414

389415
$this->connection->executeUpdate('UPDATE ' . $this->tableName . ' SET right_pos = right_pos - ? WHERE right_pos > ?',
390-
[$width, $node->getRight()]
416+
[$width, $lastNode->getRight()]
391417
);
392418
$this->connection->commit();
393419
}
394420
catch (Exception $e) {
395421
$this->connection->rollBack();
396422
throw $e;
397-
}
398-
finally {
423+
} finally {
399424
$this->connection->setAutoCommit(TRUE);
400425
}
401426

tests/Functional/DbalNestedSetTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,117 @@ public function testMoveSubTreeBefore() {
447447
$this->assertNodeMovedBefore();
448448
}
449449

450+
/**
451+
* Tests swapping parent node.
452+
*/
453+
public function testAdoptChildren() {
454+
455+
$oldParent = $this->nestedSet->getNode(new NodeKey(7, 1));
456+
$newParent = $this->nestedSet->getNode(new NodeKey(4, 1));
457+
458+
$this->nestedSet->adoptChildren($oldParent, $newParent);
459+
460+
// Check new parent has all children.
461+
$node = $this->nestedSet->getNode(new NodeKey(4, 1));
462+
$this->assertEquals(3, $node->getLeft());
463+
$this->assertEquals(12, $node->getRight());
464+
$this->assertEquals(2, $node->getDepth());
465+
466+
// Check old parent has been updated.
467+
$node = $this->nestedSet->getNode(new NodeKey(7, 1));
468+
$this->assertEquals(15, $node->getLeft());
469+
$this->assertEquals(16, $node->getRight());
470+
$this->assertEquals(2, $node->getDepth());
471+
472+
// Check first child is in correct postion.
473+
$node = $this->nestedSet->getNode(new NodeKey(5, 1));
474+
$this->assertEquals(4, $node->getLeft());
475+
$this->assertEquals(5, $node->getRight());
476+
$this->assertEquals(3, $node->getDepth());
477+
478+
// Check last child is in correct postion.
479+
$node = $this->nestedSet->getNode(new NodeKey(11, 1));
480+
$this->assertEquals(10, $node->getLeft());
481+
$this->assertEquals(11, $node->getRight());
482+
$this->assertEquals(3, $node->getDepth());
483+
}
484+
485+
/**
486+
* Tests swapping parent node.
487+
*/
488+
public function testAdoptChildrenWithDecendents() {
489+
490+
$oldParent = $this->nestedSet->getNode(new NodeKey(3, 1));
491+
$newParent = $this->nestedSet->getNode(new NodeKey(4, 1));
492+
493+
$this->nestedSet->adoptChildren($oldParent, $newParent);
494+
495+
// Check new parent has all children.
496+
$node = $this->nestedSet->getNode(new NodeKey(4, 1));
497+
$this->assertEquals(3, $node->getLeft());
498+
$this->assertEquals(18, $node->getRight());
499+
$this->assertEquals(2, $node->getDepth());
500+
501+
// Check old parent has been updated.
502+
$node = $this->nestedSet->getNode(new NodeKey(3, 1));
503+
$this->assertEquals(20, $node->getLeft());
504+
$this->assertEquals(21, $node->getRight());
505+
$this->assertEquals(1, $node->getDepth());
506+
507+
// Check first child is in correct postion.
508+
$node = $this->nestedSet->getNode(new NodeKey(5, 1));
509+
$this->assertEquals(4, $node->getLeft());
510+
$this->assertEquals(5, $node->getRight());
511+
$this->assertEquals(3, $node->getDepth());
512+
513+
// Check last child is in correct postion.
514+
$node = $this->nestedSet->getNode(new NodeKey(9, 1));
515+
$this->assertEquals(16, $node->getLeft());
516+
$this->assertEquals(17, $node->getRight());
517+
$this->assertEquals(3, $node->getDepth());
518+
}
519+
520+
/**
521+
* Tests swapping parent node.
522+
*/
523+
public function testAdoptChildrenNoExisting() {
524+
525+
$oldParent = $this->nestedSet->getNode(new NodeKey(7, 1));
526+
$newParent = $this->nestedSet->getNode(new NodeKey(8, 1));
527+
528+
echo PHP_EOL . "Before:";
529+
$this->printTree($this->nestedSet->getTree());
530+
531+
$this->nestedSet->adoptChildren($oldParent, $newParent);
532+
533+
echo PHP_EOL . "After:";
534+
$this->printTree($this->nestedSet->getTree());
535+
536+
// Check new parent has all children.
537+
$node = $this->nestedSet->getNode(new NodeKey(8, 1));
538+
$this->assertEquals(13, $node->getLeft());
539+
$this->assertEquals(18, $node->getRight());
540+
$this->assertEquals(2, $node->getDepth());
541+
542+
// Check old parent has been updated.
543+
$node = $this->nestedSet->getNode(new NodeKey(7, 1));
544+
$this->assertEquals(11, $node->getLeft());
545+
$this->assertEquals(12, $node->getRight());
546+
$this->assertEquals(2, $node->getDepth());
547+
548+
// Check first child is in correct position.
549+
$node = $this->nestedSet->getNode(new NodeKey(10, 1));
550+
$this->assertEquals(14, $node->getLeft());
551+
$this->assertEquals(15, $node->getRight());
552+
$this->assertEquals(3, $node->getDepth());
553+
554+
// Check last child is in correct position.
555+
$node = $this->nestedSet->getNode(new NodeKey(11, 1));
556+
$this->assertEquals(16, $node->getLeft());
557+
$this->assertEquals(17, $node->getRight());
558+
$this->assertEquals(3, $node->getDepth());
559+
}
560+
450561
/**
451562
* Tests inserting a root node to an empty tree.
452563
*/

0 commit comments

Comments
 (0)