Browse Source

Merge pull request #3282 from cakephp/3.0-tree-behavior-the-end

3.0 tree behavior the end
José Lorenzo Rodríguez 12 years ago
parent
commit
059c8649d8

+ 32 - 1
src/Model/Behavior/TreeBehavior.php

@@ -29,7 +29,7 @@ use Cake\ORM\Table;
  * order will be cached.
  *
  * For more information on what is a nested set and a how it works refer to
- * http://www.sitepoint.com/hierarchical-data-database
+ * http://www.sitepoint.com/hierarchical-data-database-2/
  */
 class TreeBehavior extends Behavior {
 
@@ -130,6 +130,7 @@ class TreeBehavior extends Behavior {
  */
 	public function beforeDelete(Event $event, Entity $entity) {
 		$config = $this->config();
+		$this->_ensureFields($entity);
 		$left = $entity->get($config['left']);
 		$right = $entity->get($config['right']);
 		$diff = $right - $left + 1;
@@ -157,6 +158,7 @@ class TreeBehavior extends Behavior {
 	protected function _setParent($entity, $parent) {
 		$config = $this->config();
 		$parentNode = $this->_getNode($parent);
+		$this->_ensureFields($entity);
 		$parentLeft = $parentNode->get($config['left']);
 		$parentRight = $parentNode->get($config['right']);
 		$right = $entity->get($config['right']);
@@ -215,6 +217,7 @@ class TreeBehavior extends Behavior {
 	protected function _setAsRoot($entity) {
 		$config = $this->config();
 		$edge = $this->_getMax();
+		$this->_ensureFields($entity);
 		$right = $entity->get($config['right']);
 		$left = $entity->get($config['left']);
 		$diff = $right - $left;
@@ -296,6 +299,7 @@ class TreeBehavior extends Behavior {
 				->count();
 		}
 
+		$this->_ensureFields($node);
 		return ($node->{$right} - $node->{$left} - 1) / 2;
 	}
 
@@ -386,6 +390,7 @@ class TreeBehavior extends Behavior {
  */
 	public function removeFromTree(Entity $node) {
 		return $this->_table->connection()->transactional(function() use ($node) {
+			$this->_ensureFields($node);
 			return $this->_removeFromTree($node);
 		});
 	}
@@ -442,6 +447,7 @@ class TreeBehavior extends Behavior {
  */
 	public function moveUp(Entity $node, $number = 1) {
 		return $this->_table->connection()->transactional(function() use ($node, $number) {
+			$this->_ensureFields($node);
 			return $this->_moveUp($node, $number);
 		});
 	}
@@ -516,6 +522,7 @@ class TreeBehavior extends Behavior {
  */
 	public function moveDown(Entity $node, $number = 1) {
 		return $this->_table->connection()->transactional(function() use ($node, $number) {
+			$this->_ensureFields($node);
 			return $this->_moveDown($node, $number);
 		});
 	}
@@ -722,4 +729,28 @@ class TreeBehavior extends Behavior {
 
 		return $query;
 	}
+
+/**
+ * Ensures that the provided entity contains non-empty values for the left and
+ * right fields
+ *
+ * @param \Cake\ORM\Entity $entity The entity to ensure fields for
+ * @return void
+ */
+	protected function _ensureFields($entity) {
+		$config = $this->config();
+		$fields = [$config['left'], $config['right']];
+		$values = array_filter($entity->extract($fields));
+		if (count($values) === count($fields)) {
+			return;
+		}
+
+		$fresh = $this->_table->get($entity->get($this->_table->primaryKey()), $fields);
+		$entity->set($fresh->extract($fields), ['guard' => false]);
+
+		foreach ($fields as $field) {
+			$entity->dirty($field, false);
+		}
+	}
+
 }

+ 124 - 1
tests/TestCase/Model/Behavior/TreeBehaviorTest.php

@@ -107,6 +107,20 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Tests that childCount will provide the correct lft and rght values
+ *
+ * @return void
+ */
+	public function testChildCountNoTreeColumns() {
+		$table = $this->table;
+		$node = $table->get(6);
+		$node->unsetProperty('lft');
+		$node->unsetProperty('rght');
+		$count = $this->table->childCount($node, false);
+		$this->assertEquals(4, $count);
+	}
+
+/**
  * Tests the childCount() plus callable scoping
  *
  * @return void
@@ -263,6 +277,30 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Tests moving a node with no lft and rght
+ *
+ * @return void
+ */
+	public function testMoveNoTreeColumns() {
+		$table = TableRegistry::get('MenuLinkTrees');
+		$table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
+		$node = $table->get(8);
+		$node->unsetProperty('lft');
+		$node->unsetProperty('rght');
+		$node = $table->moveUp($node, true);
+		$this->assertEquals(['lft' => 1, 'rght' => 2], $node->extract(['lft', 'rght']));
+		$nodes = $table->find()
+			->select(['id'])
+			->where(function($exp) {
+				return $exp->isNull('parent_id');
+			})
+			->where(['menu' => 'main-menu'])
+			->order(['lft' => 'ASC'])
+			->all();
+		$this->assertEquals([8, 1, 6], $nodes->extract('id')->toArray());
+	}
+
+/**
  * Tests the moveDown() method
  *
  * @return void
@@ -318,6 +356,30 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Tests moving a node with no lft and rght columns
+ *
+ * @return void
+ */
+	public function testMoveDownNoTreeColumns() {
+		$table = TableRegistry::get('MenuLinkTrees');
+		$table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
+		$node = $table->get(1);
+		$node->unsetProperty('lft');
+		$node->unsetProperty('rght');
+		$node = $table->moveDown($node, true);
+		$this->assertEquals(['lft' => 7, 'rght' => 16], $node->extract(['lft', 'rght']));
+		$nodes = $table->find()
+			->select(['id'])
+			->where(function($exp) {
+				return $exp->isNull('parent_id');
+			})
+			->where(['menu' => 'main-menu'])
+			->order(['lft' => 'ASC'])
+			->all();
+		$this->assertEquals([6, 8, 1], $nodes->extract('id')->toArray());
+	}
+
+/**
  * Tests the recover function
  *
  * @return void
@@ -495,7 +557,7 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
- * Test moving a leaft to the left
+ * Test moving a leaf to the left
  *
  * @return void
  */
@@ -514,6 +576,28 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Tests moving a subtree with a node having no lft and rght columns
+ *
+ * @return void
+ */
+	public function testReParentNoTreeColumns() {
+		$table = TableRegistry::get('NumberTrees');
+		$table->addBehavior('Tree');
+		$entity = $table->get(6);
+		$entity->unsetProperty('lft');
+		$entity->unsetProperty('rght');
+		$entity->parent_id = 2;
+		$this->assertSame($entity, $table->save($entity));
+		$this->assertEquals(9, $entity->lft);
+		$this->assertEquals(18, $entity->rght);
+
+		$result = $table->find()->order('lft')->hydrate(false)->toArray();
+		$table->recover();
+		$expected = $table->find()->order('lft')->hydrate(false)->toArray();
+		$this->assertEquals($expected, $result);
+	}
+
+/**
  * Tests moving a subtree as a new root
  *
  * @return void
@@ -533,6 +617,27 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Tests moving a subtree with no tree columns
+ *
+ * @return void
+ */
+	public function testRootingNoTreeColumns() {
+		$table = TableRegistry::get('NumberTrees');
+		$table->addBehavior('Tree');
+		$entity = $table->get(2);
+		$entity->unsetProperty('lft');
+		$entity->unsetProperty('rght');
+		$entity->parent_id = null;
+		$this->assertSame($entity, $table->save($entity));
+		$this->assertEquals(15, $entity->lft);
+		$this->assertEquals(22, $entity->rght);
+
+		$result = $table->find()->order('lft')->hydrate(false);
+		$expected = [1, 6, 7, 8, 9, 10, 11, 2, 3, 4, 5];
+		$this->assertTreeNumbers($expected, $table);
+	}
+
+/**
  * Tests that trying to create a cycle throws an exception
  *
  * @expectedException RuntimeException
@@ -596,6 +701,24 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Test deleting a node with no tree columns
+ *
+ * @return void
+ */
+	public function testDeleteRootNoTreeColumns() {
+		$table = TableRegistry::get('NumberTrees');
+		$table->addBehavior('Tree');
+		$entity = $table->get(1);
+		$entity->unsetProperty('lft');
+		$entity->unsetProperty('rght');
+		$this->assertTrue($table->delete($entity));
+		$result = $table->find()->order('lft')->hydrate(false)->toArray();
+		$table->recover();
+		$expected = $table->find()->order('lft')->hydrate(false)->toArray();
+		$this->assertEquals($expected, $result);
+	}
+
+/**
  * Tests that a leaf can be taken out of the tree and put in as a root
  *
  * @return void