Browse Source

Move nest() over

Make get() able to take arrays for $path.
mark_story 14 years ago
parent
commit
239f52c48c
2 changed files with 492 additions and 1 deletions
  1. 419 0
      lib/Cake/Test/Case/Utility/Set2Test.php
  2. 73 1
      lib/Cake/Utility/Set2.php

+ 419 - 0
lib/Cake/Test/Case/Utility/Set2Test.php

@@ -179,6 +179,9 @@ class Set2Test extends CakeTestCase {
 
 		$result = Set2::get($data, '1.Article');
 		$this->assertEquals($data[1]['Article'], $result);
+
+		$result = Set2::get($data, array('1', 'Article'));
+		$this->assertEquals($data[1]['Article'], $result);
 	}
 
 /**
@@ -1419,4 +1422,420 @@ class Set2Test extends CakeTestCase {
 	public function _reduceCallback($one, $two) {
 		return $one + $two;
 	}
+
+/**
+ * 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 = Set2::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 = Set2::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 = Set2::nest($input, array('idPath' => '{n}.id', 'parentPath' => '{n}.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 = Set2::nest($input, array('idPath' => '{n}.id', 'parentPath' => '{n}.parent_id'));
+		foreach($result as &$row) {
+			if (empty($row['children'])) {
+				unset($row['children']);
+			}
+		}
+		$this->assertEquals($input, $result);
+	}
 }

+ 73 - 1
lib/Cake/Utility/Set2.php

@@ -43,7 +43,11 @@ class Set2 {
 		if (empty($data) || empty($path)) {
 			return null;
 		}
-		$parts = explode('.', $path);
+		if (is_string($path)) {
+			$parts = explode('.', $path);
+		} else {
+			$parts = $path;
+		}
 		while (($key = array_shift($parts)) !== null) {
 			if (is_array($data) && isset($data[$key])) {
 				$data =& $data[$key];
@@ -810,4 +814,72 @@ class Set2 {
 		return $data;
 	}
 
+/**
+ * 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' => "{n}.$alias.id",
+			'parentPath' => "{n}.$alias.parent_id",
+			'children' => 'children',
+			'root' => null
+		);
+
+		$return = $idMap = array();
+		$ids = self::extract($data, $options['idPath']);
+
+		$idKeys = explode('.', $options['idPath']);
+		array_shift($idKeys);
+
+		$parentKeys = explode('.', $options['parentPath']);
+		array_shift($parentKeys);
+
+		foreach ($data as $result) {
+			$result[$options['children']] = array();
+
+			$id = self::get($result, $idKeys);
+			$parentId = self::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 = self::get($return[0], $parentKeys);
+		}
+
+		foreach ($return as $i => $result) {
+			$id = self::get($result, $idKeys);
+			$parentId = self::get($result, $parentKeys);
+			if ($id !== $root && $parentId != $root) {
+				unset($return[$i]);
+			}
+		}
+		return array_values($return);
+	}
+
 }