Skip to content

Commit

Permalink
Adding $nestingKey to Collection nest method, with tests.
Browse files Browse the repository at this point in the history
This allows for an override of the key used for nesting. Still defaults to `children`.

All of the classes using CollectionInterface were checked to make sure the method signature was update.
  • Loading branch information
darensipes committed Jul 15, 2016
1 parent 3bfa33b commit d51cfad
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 11 deletions.
3 changes: 2 additions & 1 deletion src/Collection/CollectionInterface.php
Expand Up @@ -620,9 +620,10 @@ public function combine($keyPath, $valuePath, $groupPath = null);
* whether an element is parent of another
* @param callable|string $parentPath the column name path to use for determining
* whether an element is child of another
* @param string $nestingKey The key name under which children are nested
* @return \Cake\Collection\CollectionInterface
*/
public function nest($idPath, $parentPath);
public function nest($idPath, $parentPath, $nestingKey = 'children');

/**
* Returns a new collection containing each of the elements found in `$values` as
Expand Down
11 changes: 5 additions & 6 deletions src/Collection/CollectionTrait.php
Expand Up @@ -422,22 +422,22 @@ public function combine($keyPath, $valuePath, $groupPath = null)
* {@inheritDoc}
*
*/
public function nest($idPath, $parentPath)
public function nest($idPath, $parentPath, $nestingKey = 'children')
{
$parents = [];
$idPath = $this->_propertyExtractor($idPath);
$parentPath = $this->_propertyExtractor($parentPath);
$isObject = true;

$mapper = function ($row, $key, $mapReduce) use (&$parents, $idPath, $parentPath) {
$row['children'] = [];
$mapper = function ($row, $key, $mapReduce) use (&$parents, $idPath, $parentPath, $nestingKey) {
$row[$nestingKey] = [];
$id = $idPath($row, $key);
$parentId = $parentPath($row, $key);
$parents[$id] =& $row;
$mapReduce->emitIntermediate($id, $parentId);
};

$reducer = function ($values, $key, $mapReduce) use (&$parents, &$isObject) {
$reducer = function ($values, $key, $mapReduce) use (&$parents, &$isObject, $nestingKey) {
static $foundOutType = false;
if (!$foundOutType) {
$isObject = is_object(current($parents));
Expand All @@ -448,15 +448,14 @@ public function nest($idPath, $parentPath)
$parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1);
$mapReduce->emit($parents[$id]);
}

return null;
}

$children = [];
foreach ($values as $id) {
$children[] =& $parents[$id];
}
$parents[$key]['children'] = $children;
$parents[$key][$nestingKey] = $children;
};

return (new Collection(new MapReduce($this->unwrap(), $mapper, $reducer)))
Expand Down
4 changes: 2 additions & 2 deletions src/ORM/Query.php
Expand Up @@ -56,8 +56,8 @@
* @method \Cake\Collection\CollectionInterface append(array|Traversable $items) Appends more rows to the result of the query.
* @method \Cake\Collection\CollectionInterface combine($k, $v, $g = null) Returns the values of the column $v index by column $k,
* and grouped by $g.
* @method \Cake\Collection\CollectionInterface nest($k, $p) Creates a tree structure by nesting the values of column $p into that
* with the same value for $k.
* @method \Cake\Collection\CollectionInterface nest($k, $p, $n = 'children') Creates a tree structure by nesting the values of column $p into that
* with the same value for $k using $n as the nesting key.
* @method array toArray() Returns a key-value array with the results of this query.
* @method array toList() Returns a numerically indexed array with the results of this query.
* @method \Cake\Collection\CollectionInterface stopWhen(callable $c) Returns each row until the callable returns true.
Expand Down
6 changes: 4 additions & 2 deletions src/ORM/Table.php
Expand Up @@ -1085,13 +1085,14 @@ public function findList(Query $query, array $options)
*
* You can customize what fields are used for nesting results, by default the
* primary key and the `parent_id` fields are used. If you wish to change
* these defaults you need to provide the keys `keyField` or `parentField` in
* these defaults you need to provide the keys `keyField`, `parentField` or `childrenKey` in
* `$options`:
*
* ```
* $table->find('threaded', [
* 'keyField' => 'id',
* 'parentField' => 'ancestor_id'
* 'childrenKey' => 'children'
* ]);
* ```
*
Expand All @@ -1104,6 +1105,7 @@ public function findThreaded(Query $query, array $options)
$options += [
'keyField' => $this->primaryKey(),
'parentField' => 'parent_id',
'nestingKey' => 'children'
];

if (isset($options['idField'])) {
Expand All @@ -1115,7 +1117,7 @@ public function findThreaded(Query $query, array $options)
$options = $this->_setFieldMatchers($options, ['keyField', 'parentField']);

return $query->formatResults(function ($results) use ($options) {
return $results->nest($options['keyField'], $options['parentField']);
return $results->nest($options['keyField'], $options['parentField'], $options['nestingKey']);
});
}

Expand Down
165 changes: 165 additions & 0 deletions tests/TestCase/Collection/CollectionTest.php
Expand Up @@ -913,12 +913,117 @@ public function testNest()
$this->assertEquals($expected, $collection->toArray());
}

/**
* Tests the nest method with alternate nesting key
*
* @return void
*/
public function testNestAlternateNestingKey()
{
$items = [
['id' => 1, 'parent_id' => null],
['id' => 2, 'parent_id' => 1],
['id' => 3, 'parent_id' => 1],
['id' => 4, 'parent_id' => 1],
['id' => 5, 'parent_id' => 6],
['id' => 6, 'parent_id' => null],
['id' => 7, 'parent_id' => 1],
['id' => 8, 'parent_id' => 6],
['id' => 9, 'parent_id' => 6],
['id' => 10, 'parent_id' => 6]
];
$collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
$expected = [
[
'id' => 1,
'parent_id' => null,
'nodes' => [
['id' => 2, 'parent_id' => 1, 'nodes' => []],
['id' => 3, 'parent_id' => 1, 'nodes' => []],
['id' => 4, 'parent_id' => 1, 'nodes' => []],
['id' => 7, 'parent_id' => 1, 'nodes' => []]
]
],
[
'id' => 6,
'parent_id' => null,
'nodes' => [
['id' => 5, 'parent_id' => 6, 'nodes' => []],
['id' => 8, 'parent_id' => 6, 'nodes' => []],
['id' => 9, 'parent_id' => 6, 'nodes' => []],
['id' => 10, 'parent_id' => 6, 'nodes' => []]
]
]
];
$this->assertEquals($expected, $collection->toArray());
}

/**
* Tests the nest method with more than one level
*
* @return void
*/
public function testNestMultiLevel()
{
$items = [
['id' => 1, 'parent_id' => null],
['id' => 2, 'parent_id' => 1],
['id' => 3, 'parent_id' => 2],
['id' => 4, 'parent_id' => 2],
['id' => 5, 'parent_id' => 3],
['id' => 6, 'parent_id' => null],
['id' => 7, 'parent_id' => 3],
['id' => 8, 'parent_id' => 4],
['id' => 9, 'parent_id' => 6],
['id' => 10, 'parent_id' => 6]
];
$collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
$expected = [
[
'id' => 1,
'parent_id' => null,
'nodes' => [
[
'id' => 2,
'parent_id' => 1,
'nodes' => [
[
'id' => 3,
'parent_id' => 2,
'nodes' => [
['id' => 5, 'parent_id' => 3, 'nodes' => []],
['id' => 7, 'parent_id' => 3, 'nodes' => []]
]
],
[
'id' => 4,
'parent_id' => 2,
'nodes' => [
['id' => 8, 'parent_id' => 4, 'nodes' => []]
]
]
]
]
]
],
[
'id' => 6,
'parent_id' => null,
'nodes' => [
['id' => 9, 'parent_id' => 6, 'nodes' => []],
['id' => 10, 'parent_id' => 6, 'nodes' => []]
]
]
];
$this->assertEquals($expected, $collection->toArray());
}

/**
* Tests the nest method with more than one level
*
* @return void
*/
public function testNestMultiLevelAlternateNestingKey()
{
$items = [
['id' => 1, 'parent_id' => null],
Expand Down Expand Up @@ -1033,6 +1138,66 @@ public function testNestObjects()
$this->assertEquals($expected, $collection->toArray());
}

/**
* Tests the nest method with more than one level
*
* @return void
*/
public function testNestObjectsAlternateNestingKey()
{
$items = [
new ArrayObject(['id' => 1, 'parent_id' => null]),
new ArrayObject(['id' => 2, 'parent_id' => 1]),
new ArrayObject(['id' => 3, 'parent_id' => 2]),
new ArrayObject(['id' => 4, 'parent_id' => 2]),
new ArrayObject(['id' => 5, 'parent_id' => 3]),
new ArrayObject(['id' => 6, 'parent_id' => null]),
new ArrayObject(['id' => 7, 'parent_id' => 3]),
new ArrayObject(['id' => 8, 'parent_id' => 4]),
new ArrayObject(['id' => 9, 'parent_id' => 6]),
new ArrayObject(['id' => 10, 'parent_id' => 6])
];
$collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
$expected = [
new ArrayObject([
'id' => 1,
'parent_id' => null,
'nodes' => [
new ArrayObject([
'id' => 2,
'parent_id' => 1,
'nodes' => [
new ArrayObject([
'id' => 3,
'parent_id' => 2,
'nodes' => [
new ArrayObject(['id' => 5, 'parent_id' => 3, 'nodes' => []]),
new ArrayObject(['id' => 7, 'parent_id' => 3, 'nodes' => []])
]
]),
new ArrayObject([
'id' => 4,
'parent_id' => 2,
'nodes' => [
new ArrayObject(['id' => 8, 'parent_id' => 4, 'nodes' => []])
]
])
]
])
]
]),
new ArrayObject([
'id' => 6,
'parent_id' => null,
'nodes' => [
new ArrayObject(['id' => 9, 'parent_id' => 6, 'nodes' => []]),
new ArrayObject(['id' => 10, 'parent_id' => 6, 'nodes' => []])
]
])
];
$this->assertEquals($expected, $collection->toArray());
}

/**
* Tests insert
*
Expand Down

0 comments on commit d51cfad

Please sign in to comment.