Browse Source

Merge pull request #3266 from cakephp/3.0-tree-behavior-pt3

3.0 tree behavior pt3
José Lorenzo Rodríguez 12 years ago
parent
commit
91d0e5966f

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

@@ -19,6 +19,18 @@ use Cake\ORM\Behavior;
 use Cake\ORM\Entity;
 use Cake\ORM\Table;
 
+/**
+ * Makes the table to which this is attached to behave like a nested set and
+ * provides methods for managing and retrieving information out of the derived
+ * hierarchical structure.
+ *
+ * Tables attaching this behavior are required to have a column referencing the
+ * parent row, and two other numeric columns (lft and rght) where the implicit
+ * 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
+ */
 class TreeBehavior extends Behavior {
 
 /**
@@ -39,6 +51,7 @@ class TreeBehavior extends Behavior {
 		'implementedFinders' => [
 			'path' => 'findPath',
 			'children' => 'findChildren',
+			'treeList' => 'findTreeList'
 		],
 		'implementedMethods' => [
 			'childCount' => 'childCount',
@@ -297,6 +310,7 @@ class TreeBehavior extends Behavior {
  *
  * If the direct option is set to true, only the direct children are returned (based upon the parent_id field)
  *
+ * @param \Cake\ORM\Query $query
  * @param array $options Array of options as described above
  * @return \Cake\ORM\Query
  * @throws \InvalidArgumentException When the 'for' key is not passed in $options
@@ -328,8 +342,40 @@ class TreeBehavior extends Behavior {
 	}
 
 /**
+ * Gets a representation of the elements in the tree as a flat list where the keys are
+ * the primary key for the table and the values are the display field for the table. 
+ * Values are prefixed to visually indicate relative depth in the tree.
+ *
+ * Avaliable options are:
+ *
+ * - keyPath: A dot separated path to fetch the field to use for the array key, or a closure to
+ *  return the key out of the provided row.
+ * - valuePath: A dot separated path to fetch the field to use for the array value, or a closure to
+ *  return the value out of the provided row.
+ *  - spacer: A string to be used as prefix for denoting the depth in the tree for each item
+ *
+ * @param \Cake\ORM\Query $query
+ * @param array $options Array of options as described above
+ * @return \Cake\ORM\Query
+ */
+	public function findTreeList($query, $options) {
+		return $this->_scope($query)
+			->find('threaded', ['parentField' => $this->config()['parent']])
+			->formatResults(function($results) use ($options) {
+				$options += [
+					'keyPath' => $this->_table->primaryKey(),
+					'valuePath' => $this->_table->displayField(),
+					'spacer' => '_'
+				];
+				return $results
+					->listNested()
+					->printer($options['valuePath'], $options['keyPath'], $options['spacer']);
+			});
+	}
+
+/**
  * Removes the current node from the tree, by positioning it as a new root
- * and reparents all children up one level.
+ * and re-parents all children up one level.
  *
  * Note that the node will not be deleted just moved away from its current position
  * without moving its children with it.
@@ -339,6 +385,19 @@ class TreeBehavior extends Behavior {
  * false on error
  */
 	public function removeFromTree(Entity $node) {
+		return $this->_table->connection()->transactional(function() use ($node) {
+			return $this->_removeFromTree($node);
+		});
+	}
+
+/**
+ * Helper function containing the actual code for removeFromTree
+ *
+ * @param \Cake\ORM\Entity $node The node to remove from the tree
+ * @return \Cake\ORM\Entity|false the node after being removed from the tree or
+ * false on error
+ */
+	protected function _removeFromTree($node) {
 		$config = $this->config();
 		$left = $node->get($config['left']);
 		$right = $node->get($config['right']);
@@ -382,6 +441,20 @@ class TreeBehavior extends Behavior {
  * @return \Cake\ORM\Entity|boolean $node The node after being moved or false on failure
  */
 	public function moveUp(Entity $node, $number = 1) {
+		return $this->_table->connection()->transactional(function() use ($node, $number) {
+			return $this->_moveUp($node, $number);
+		});
+	}
+
+/**
+ * Helper function used with the actual code for moveUp
+ *
+ * @param \Cake\ORM\Entity $node The node to move
+ * @param integer|boolean $number How many places to move the node, or true to move to first position
+ * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
+ * @return \Cake\ORM\Entity|boolean $node The node after being moved or false on failure
+ */
+	protected function _moveUp($node, $number) {
 		$config = $this->config();
 		list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
 
@@ -442,6 +515,20 @@ class TreeBehavior extends Behavior {
  * @return \Cake\ORM\Entity|boolean the entity after being moved or false on failure
  */
 	public function moveDown(Entity $node, $number = 1) {
+		return $this->_table->connection()->transactional(function() use ($node, $number) {
+			return $this->_moveDown($node, $number);
+		});
+	}
+
+/**
+ * Helper function used with the actual code for moveDown
+ *
+ * @param \Cake\ORM\Entity $node The node to move
+ * @param integer|boolean $number How many places to move the node, or true to move to last position
+ * @throws \Cake\ORM\Error\RecordNotFoundException When node was not found
+ * @return \Cake\ORM\Entity|boolean $node The node after being moved or false on failure
+ */
+	protected function _moveDown($node, $number) {
 		$config = $this->config();
 		list($parent, $left, $right) = [$config['parent'], $config['left'], $config['right']];
 

+ 46 - 0
tests/TestCase/Model/Behavior/TreeBehaviorTest.php

@@ -159,6 +159,52 @@ class TreeBehaviorTest extends TestCase {
 	}
 
 /**
+ * Tests the find('treeList') method
+ *
+ * @return void
+ */
+	public function testFindTreeList() {
+		$table = TableRegistry::get('MenuLinkTrees');
+		$table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
+		$result = $table->find('treeList')->toArray();
+		$expected = [
+			1 => 'Link 1',
+			2 => '_Link 2',
+			3 => '_Link 3',
+			4 => '__Link 4',
+			5 => '___Link 5',
+			6 => 'Link 6',
+			7 => '_Link 7',
+			8 => 'Link 8'
+		];
+		$this->assertEquals($expected, $result);
+	}
+
+/**
+ * Tests the find('treeList') method with custom options
+ *
+ * @return void
+ */
+	public function testFindTreeListCustom() {
+		$table = TableRegistry::get('MenuLinkTrees');
+		$table->addBehavior('Tree', ['scope' => ['menu' => 'main-menu']]);
+		$result = $table
+			->find('treeList', ['keyPath' => 'url', 'valuePath' => 'id', 'spacer' => ' '])
+			->toArray();
+		$expected = [
+			'/link1.html' => '1',
+			'http://example.com' => ' 2',
+			'/what/even-more-links.html' => ' 3',
+			'/lorem/ipsum.html' => '  4',
+			'/what/the.html' => '   5',
+			'/yeah/another-link.html' => '6',
+			'http://cakephp.org' => ' 7',
+			'/page/who-we-are.html' => '8'
+		];
+		$this->assertEquals($expected, $result);
+	}
+
+/**
  * Tests the moveUp() method
  *
  * @return void