Browse Source

Merge branch 'feature/set-nest' into 2.1

Conflicts:
	lib/Cake/Model/Model.php
AD7six 14 years ago
parent
commit
a5240e23ab

+ 4 - 38
lib/Cake/Model/Model.php

@@ -2812,44 +2812,10 @@ class Model extends Object implements CakeEventListener {
 		if ($state === 'before') {
 			return $query;
 		} elseif ($state === 'after') {
-			$return = $idMap = array();
-			$ids = Set::extract($results, '{n}.' . $this->alias . '.' . $this->primaryKey);
-
-			if (isset($results[0][$this->alias]) && !array_key_exists('parent_id', $results[0][$this->alias])) {
-				trigger_error(
-					__d('cake_dev', 'You cannot use find("threaded") on models without a "parent_id" field.'),
-					E_USER_WARNING
-				);
-				return $return;
-			}
-
-			foreach ($results as $result) {
-				$result['children'] = array();
-				$id = $result[$this->alias][$this->primaryKey];
-				$parentId = $result[$this->alias]['parent_id'];
-				if (isset($idMap[$id]['children'])) {
-					$idMap[$id] = array_merge($result, (array)$idMap[$id]);
-				} else {
-					$idMap[$id] = array_merge($result, array('children' => array()));
-				}
-				if (!$parentId || !in_array($parentId, $ids)) {
-					$return[] =& $idMap[$id];
-				} else {
-					$idMap[$parentId]['children'][] =& $idMap[$id];
-				}
-			}
-			if (count($return) > 1) {
-				$ids = array_unique(Set::extract('/' . $this->alias . '/parent_id', $return));
-				if (count($ids) > 1) {
-					$root = $return[0][$this->alias]['parent_id'];
-					foreach ($return as $key => $value) {
-						if ($value[$this->alias]['parent_id'] != $root) {
-							unset($return[$key]);
-						}
-					}
-				}
-			}
-			return $return;
+			return Set::nest($results, array(
+				'idPath' => '/' . $this->alias . '/' . $this->primaryKey,
+				'parentPath' => '/' . $this->alias . '/parent_id'
+			));
 		}
 	}
 

+ 0 - 12
lib/Cake/Test/Case/Model/ModelReadTest.php

@@ -2991,18 +2991,6 @@ class ModelReadTest extends BaseModelTest {
 	}
 
 /**
- * find(threaded) should trigger errors whne there is no parent_id field.
- *
- * @expectedException PHPUnit_Framework_Error_Warning
- * @return void
- */
-	public function testFindThreadedError() {
-		$this->loadFixtures('Apple', 'Sample');
-		$Apple = new Apple();
-		$Apple->find('threaded');
-	}
-
-/**
  * testFindAllThreaded method
  *
  * @return void

+ 416 - 0
lib/Cake/Test/Case/Utility/SetTest.php

@@ -3137,4 +3137,420 @@ class SetTest extends CakeTestCase {
 		$expected = array('one' => array('a', 'b', 'c' => 'cee'), 'two' => 2, 'three' => null);
 		$this->assertEquals($expected, $result);
 	}
+
+/**
+ * test Set nest with a normal model result set. For kicks rely on Set nest detecting the key names
+ * automatically
+ *
+ * @return void
+ */
+	public function testNestModel() {
+		$input = array(
+			array(
+				'ModelName' => array(
+					'id' => 1,
+					'parent_id' => null
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 2,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 3,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 4,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 5,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 6,
+					'parent_id' => null
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 7,
+					'parent_id' => 6
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 8,
+					'parent_id' => 6
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 9,
+					'parent_id' => 6
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 10,
+					'parent_id' => 6
+				)
+			)
+		);
+		$expected = array(
+			array(
+				'ModelName' => array(
+					'id' => 1,
+					'parent_id' => null
+				),
+				'children' => array(
+					array(
+						'ModelName' => array(
+							'id' => 2,
+							'parent_id' => 1
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 3,
+							'parent_id' => 1
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 4,
+							'parent_id' => 1
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 5,
+							'parent_id' => 1
+						),
+						'children' => array()
+					),
+
+				)
+			),
+			array(
+				'ModelName' => array(
+					'id' => 6,
+					'parent_id' => null
+				),
+				'children' => array(
+					array(
+						'ModelName' => array(
+							'id' => 7,
+							'parent_id' => 6
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 8,
+							'parent_id' => 6
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 9,
+							'parent_id' => 6
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 10,
+							'parent_id' => 6
+						),
+						'children' => array()
+					)
+				)
+			)
+		);
+		$result = Set::nest($input);
+		$this->assertEquals($expected, $result);
+	}
+
+/**
+ * test Set nest with a normal model result set, and a nominated root id
+ *
+ * @return void
+ */
+	public function testNestModelExplicitRoot() {
+		$input = array(
+			array(
+				'ModelName' => array(
+					'id' => 1,
+					'parent_id' => null
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 2,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 3,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 4,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 5,
+					'parent_id' => 1
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 6,
+					'parent_id' => null
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 7,
+					'parent_id' => 6
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 8,
+					'parent_id' => 6
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 9,
+					'parent_id' => 6
+				),
+			),
+			array(
+				'ModelName' => array(
+					'id' => 10,
+					'parent_id' => 6
+				)
+			)
+		);
+		$expected = array(
+			array(
+				'ModelName' => array(
+					'id' => 6,
+					'parent_id' => null
+				),
+				'children' => array(
+					array(
+						'ModelName' => array(
+							'id' => 7,
+							'parent_id' => 6
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 8,
+							'parent_id' => 6
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 9,
+							'parent_id' => 6
+						),
+						'children' => array()
+					),
+					array(
+						'ModelName' => array(
+							'id' => 10,
+							'parent_id' => 6
+						),
+						'children' => array()
+					)
+				)
+			)
+		);
+		$result = Set::nest($input, array('root' => 6));
+		$this->assertEquals($expected, $result);
+	}
+
+/**
+ * test Set nest with a 1d array - this method should be able to handle any type of array input
+ *
+ * @return void
+ */
+	public function testNest1Dimensional() {
+		$input = array(
+			array(
+				'id' => 1,
+				'parent_id' => null
+			),
+			array(
+				'id' => 2,
+				'parent_id' => 1
+			),
+			array(
+				'id' => 3,
+				'parent_id' => 1
+			),
+			array(
+				'id' => 4,
+				'parent_id' => 1
+			),
+			array(
+				'id' => 5,
+				'parent_id' => 1
+			),
+			array(
+				'id' => 6,
+				'parent_id' => null
+			),
+			array(
+				'id' => 7,
+				'parent_id' => 6
+			),
+			array(
+				'id' => 8,
+				'parent_id' => 6
+			),
+			array(
+				'id' => 9,
+				'parent_id' => 6
+			),
+			array(
+				'id' => 10,
+				'parent_id' => 6
+			)
+		);
+		$expected = array(
+			array(
+				'id' => 1,
+				'parent_id' => null,
+				'children' => array(
+					array(
+						'id' => 2,
+						'parent_id' => 1,
+						'children' => array()
+					),
+					array(
+						'id' => 3,
+						'parent_id' => 1,
+						'children' => array()
+					),
+					array(
+						'id' => 4,
+						'parent_id' => 1,
+						'children' => array()
+					),
+					array(
+						'id' => 5,
+						'parent_id' => 1,
+						'children' => array()
+					),
+
+				)
+			),
+			array(
+				'id' => 6,
+				'parent_id' => null,
+				'children' => array(
+					array(
+						'id' => 7,
+						'parent_id' => 6,
+						'children' => array()
+					),
+					array(
+						'id' => 8,
+						'parent_id' => 6,
+						'children' => array()
+					),
+					array(
+						'id' => 9,
+						'parent_id' => 6,
+						'children' => array()
+					),
+					array(
+						'id' => 10,
+						'parent_id' => 6,
+						'children' => array()
+					)
+				)
+			)
+		);
+		$result = Set::nest($input, array('idPath' => '/id', 'parentPath' => '/parent_id'));
+		$this->assertEquals($expected, $result);
+	}
+
+/**
+ * test Set nest with no specified parent data.
+ *
+ * The result should be the same as the input.
+ * For an easier comparison, unset all the empty children arrays from the result
+ *
+ * @return void
+ */
+	public function testMissingParent() {
+		$input = array(
+			array(
+				'id' => 1,
+			),
+			array(
+				'id' => 2,
+			),
+			array(
+				'id' => 3,
+			),
+			array(
+				'id' => 4,
+			),
+			array(
+				'id' => 5,
+			),
+			array(
+				'id' => 6,
+			),
+			array(
+				'id' => 7,
+			),
+			array(
+				'id' => 8,
+			),
+			array(
+				'id' => 9,
+			),
+			array(
+				'id' => 10,
+			)
+		);
+
+		$result = Set::nest($input, array('idPath' => '/id', 'parentPath' => '/parent_id'));
+		foreach($result as &$row) {
+			if (empty($row['children'])) {
+				unset($row['children']);
+			}
+		}
+		$this->assertEquals($input, $result);
+	}
 }

+ 96 - 0
lib/Cake/Utility/Set.php

@@ -1114,4 +1114,100 @@ class Set {
 		}
 		return null;
 	}
+
+/**
+ * Takes in a flat array and returns a nested array
+ *
+ * @param mixed $data
+ * @param array $options Options are:
+ *      children   - the key name to use in the resultset for children
+ *      idPath     - the path to a key that identifies each entry
+ *      parentPath - the path to a key that identifies the parent of each entry
+ *      root       - the id of the desired top-most result
+ * @return array of results, nested
+ * @link
+ */
+	public static function nest($data, $options = array()) {
+		if (!$data) {
+			return $data;
+		}
+
+		$alias = key(current($data));
+		$options += array(
+			'idPath' => "/$alias/id",
+			'parentPath' => "/$alias/parent_id",
+			'children' => 'children',
+			'root' => null
+		);
+
+		$return = $idMap = array();
+		$ids = Set::extract($data, $options['idPath']);
+		$idKeys = explode('/', trim($options['idPath'], '/'));
+		$parentKeys = explode('/', trim($options['parentPath'], '/'));
+
+		foreach ($data as $result) {
+			$result[$options['children']] = array();
+
+			$id = Set::get($result, $idKeys);
+			$parentId = Set::get($result, $parentKeys);
+
+			if (isset($idMap[$id][$options['children']])) {
+				$idMap[$id] = array_merge($result, (array)$idMap[$id]);
+			} else {
+				$idMap[$id] = array_merge($result, array($options['children'] => array()));
+			}
+			if (!$parentId || !in_array($parentId, $ids)) {
+				$return[] =& $idMap[$id];
+			} else {
+				$idMap[$parentId][$options['children']][] =& $idMap[$id];
+			}
+		}
+
+		if ($options['root']) {
+			$root = $options['root'];
+		} else {
+			$root = Set::get($return[0], $parentKeys);
+		}
+
+		foreach ($return as $i => $result) {
+			$id = Set::get($result, $idKeys);
+			$parentId = Set::get($result, $parentKeys);
+			if ($id !== $root && $parentId != $root) {
+				unset($return[$i]);
+			}
+		}
+
+		return array_values($return);
+	}
+
+/**
+ * Return the value at the specified position
+ *
+ * @param mixed $input an array
+ * @param mixed $path string or array of array keys
+ * @return the value at the specified position or null if it doesn't exist
+ */
+	public static function get($input, $path = null) {
+		if (is_string($path)) {
+			if (strpos($path, '/') !== false) {
+				$keys = explode('/', trim($path, '/'));
+			} else {
+				$keys = explode('.', trim($path, '.'));
+			}
+		} else {
+			$keys = $path;
+		}
+		if (!$keys) {
+			return $input;
+		}
+
+		$return = $input;
+		foreach($keys as $key) {
+			if (!isset($return[$key])) {
+				return null;
+			}
+			$return = $return[$key];
+		}
+		return $return;
+	}
 }