From edefed1be5d89ee923b6466f2c82e341f031d4b9 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 23 Sep 2014 21:46:57 +0200 Subject: [PATCH] Extracting collection definition into an interface --- src/Collection/Collection.php | 12 +- src/Collection/CollectionTrait.php | 576 ++--------------------------- 2 files changed, 33 insertions(+), 555 deletions(-) diff --git a/src/Collection/Collection.php b/src/Collection/Collection.php index 25c712bf52f..f0dcf7d3248 100644 --- a/src/Collection/Collection.php +++ b/src/Collection/Collection.php @@ -15,6 +15,7 @@ namespace Cake\Collection; use ArrayIterator; +use Cake\Collection\CollectionInterface; use Cake\Collection\CollectionTrait; use InvalidArgumentException; use IteratorIterator; @@ -24,7 +25,7 @@ * A collection is an immutable list of elements with a handful of functions to * iterate, group, transform and extract information from it. */ -class Collection extends IteratorIterator implements JsonSerializable { +class Collection extends IteratorIterator implements CollectionInterface { use CollectionTrait; @@ -47,13 +48,4 @@ public function __construct($items) { parent::__construct($items); } -/** - * Returns the iterator with this collection's elements - * - * @return \Traversable - */ - public function getIterator() { - return $this->_iterator; - } - } diff --git a/src/Collection/CollectionTrait.php b/src/Collection/CollectionTrait.php index a7eb55fff17..ba77c2edd66 100644 --- a/src/Collection/CollectionTrait.php +++ b/src/Collection/CollectionTrait.php @@ -36,21 +36,8 @@ trait CollectionTrait { use ExtractTrait; /** - * Executes the passed callable for each of the elements in this collection - * and passes both the value and key for them on each step. - * Returns the same collection for chaining. + * {@inheritDoc} * - * ###Example: - * - * {{{ - * $collection = (new Collection($items))->each(function ($value, $key) { - * echo "Element $key: $value"; - * }); - * }}} - * - * @param callable $c callable function that will receive each of the elements - * in this collection - * @return \Cake\Collection\Collection */ public function each(callable $c) { foreach ($this as $k => $v) { @@ -60,28 +47,8 @@ public function each(callable $c) { } /** - * Looks through each value in the collection, and returns another collection with - * all the values that pass a truth test. Only the values for which the callback - * returns true will be present in the resulting collection. - * - * Each time the callback is executed it will receive the value of the element - * in the current iteration, the key of the element and this collection as - * arguments, in that order. - * - * ##Example: - * - * Filtering odd numbers in an array, at the end only the value 2 will - * be present in the resulting collection: - * - * {{{ - * $collection = (new Collection([1, 2, 3]))->filter(function ($value, $key) { - * return $value % 2 === 0; - * }); - * }}} + * {@inheritDoc} * - * @param callable $c the method that will receive each of the elements and - * returns true whether or not they should be in the resulting collection. - * If left null, a callback that filters out falsey values will be used. * @return \Cake\Collection\Iterator\FilterIterator */ public function filter(callable $c = null) { @@ -94,26 +61,8 @@ public function filter(callable $c = null) { } /** - * Looks through each value in the collection, and returns another collection with - * all the values that do not pass a truth test. This is the opposite of `filter`. + * {@inheritDoc} * - * Each time the callback is executed it will receive the value of the element - * in the current iteration, the key of the element and this collection as - * arguments, in that order. - * - * ##Example: - * - * Filtering even numbers in an array, at the end only values 1 and 3 will - * be present in the resulting collection: - * - * {{{ - * $collection = (new Collection([1, 2, 3]))->reject(function ($value, $key) { - * return $value % 2 === 0; - * }); - * }}} - * - * @param callable $c the method that will receive each of the elements and - * returns true whether or not they should be out of the resulting collection. * @return \Cake\Collection\Iterator\FilterIterator */ public function reject(callable $c) { @@ -123,24 +72,8 @@ public function reject(callable $c) { } /** - * Returns true if all values in this collection pass the truth test provided - * in the callback. - * - * Each time the callback is executed it will receive the value of the element - * in the current iteration and the key of the element as arguments, in that - * order. - * - * ###Example: + * {@inheritDoc} * - * {{{ - * $overTwentyOne = (new Collection([24, 45, 60, 15]))->every(function ($value, $key) { - * return $value > 21; - * }); - * }}} - * - * @param callable $c a callback function - * @return bool true if for all elements in this collection the provided - * callback returns true, false otherwise */ public function every(callable $c) { foreach ($this as $key => $value) { @@ -152,24 +85,8 @@ public function every(callable $c) { } /** - * Returns true if any of the values in this collection pass the truth test - * provided in the callback. - * - * Each time the callback is executed it will receive the value of the element - * in the current iteration and the key of the element as arguments, in that - * order. - * - * ###Example: + * {@inheritDoc} * - * {{{ - * $hasYoungPeople = (new Collection([24, 45, 15]))->every(function ($value, $key) { - * return $value < 21; - * }); - * }}} - * - * @param callable $c a callback function - * @return bool true if for all elements in this collection the provided - * callback returns true, false otherwise */ public function some(callable $c) { foreach ($this as $key => $value) { @@ -181,11 +98,8 @@ public function some(callable $c) { } /** - * Returns true if $value is present in this collection. Comparisons are made - * both by value and type. + * {@inheritDoc} * - * @param mixed $value The value to check for - * @return bool true if $value is present in this collection */ public function contains($value) { foreach ($this as $v) { @@ -197,25 +111,8 @@ public function contains($value) { } /** - * Returns another collection after modifying each of the values in this one using - * the provided callable. - * - * Each time the callback is executed it will receive the value of the element - * in the current iteration, the key of the element and this collection as - * arguments, in that order. - * - * ##Example: + * {@inheritDoc} * - * Getting a collection of booleans where true indicates if a person is female: - * - * {{{ - * $collection = (new Collection($people))->map(function ($person, $key) { - * return $person->gender === 'female'; - * }); - * }}} - * - * @param callable $c the method that will receive each of the elements and - * returns the new value for the key that is being iterated * @return \Cake\Collection\Iterator\ReplaceIterator */ public function map(callable $c) { @@ -223,14 +120,8 @@ public function map(callable $c) { } /** - * Folds the values in this collection to a single value, as the result of - * applying the callback function to all elements. $zero is the initial state - * of the reduction, and each successive step of it should be returned - * by the callback function. + * {@inheritDoc} * - * @param callable $c The callback function to be called - * @param mixed $zero The state of reduction - * @return void */ public function reduce(callable $c, $zero) { $result = $zero; @@ -241,32 +132,8 @@ public function reduce(callable $c, $zero) { } /** - * Returns a new collection containing the column or property value found in each - * of the elements, as requested in the $matcher param. - * - * The matcher can be a string with a property name to extract or a dot separated - * path of properties that should be followed to get the last one in the path. - * - * If a column or property could not be found for a particular element in the - * collection, that position is filled with null. - * - * ### Example: + * {@inheritDoc} * - * Extract the user name for all comments in the array: - * - * {{{ - * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] - * ]; - * $extracted = (new Collection($items))->extract('comment.user.name'); - * - * // Result will look like this when converted to array - * ['Mark', 'Renan'] - * }}} - * - * @param string $matcher a dot separated string symbolizing the path to follow - * inside the hierarchy of each value so that the column can be extracted. * @return \Cake\Collection\Iterator\ExtractIterator */ public function extract($matcher) { @@ -274,28 +141,8 @@ public function extract($matcher) { } /** - * Returns the top element in this collection after being sorted by a property. - * Check the sortBy method for information on the callback and $type parameters - * - * ###Examples: - * - * {{{ - * // For a collection of employees - * $max = $collection->max('age'); - * $max = $collection->max('user.salary'); - * $max = $collection->max(function ($e) { - * return $e->get('user')->get('salary'); - * }); - * - * // Display employee name - * echo $max->name; - * }}} + * {@inheritDoc} * - * @param callable|string $callback the callback or column name to use for sorting - * @param int $type the type of comparison to perform, either SORT_STRING - * SORT_NUMERIC or SORT_NATURAL - * @see \Cake\Collection\Collection::sortBy() - * @return mixed The value of the top element in the collection */ public function max($callback, $type = SORT_NUMERIC) { $sorted = new SortIterator($this, $callback, SORT_DESC, $type); @@ -303,28 +150,8 @@ public function max($callback, $type = SORT_NUMERIC) { } /** - * Returns the bottom element in this collection after being sorted by a property. - * Check the sortBy method for information on the callback and $type parameters + * {@inheritDoc} * - * ###Examples: - * - * {{{ - * // For a collection of employees - * $min = $collection->min('age'); - * $min = $collection->min('user.salary'); - * $min = $collection->min(function ($e) { - * return $e->get('user')->get('salary'); - * }); - * - * // Display employee name - * echo $min->name; - * }}} - * - * @param callable|string $callback the callback or column name to use for sorting - * @param int $type the type of comparison to perform, either SORT_STRING - * SORT_NUMERIC or SORT_NATURAL - * @see \Cake\Collection\Collection::sortBy() - * @return mixed The value of the bottom element in the collection */ public function min($callback, $type = SORT_NUMERIC) { $sorted = new SortIterator($this, $callback, SORT_ASC, $type); @@ -332,85 +159,16 @@ public function min($callback, $type = SORT_NUMERIC) { } /** - * Returns a sorted iterator out of the elements in this colletion, - * ranked in ascending order by the results of running each value through a - * callback. $callback can also be a string representing the column or property - * name. - * - * The callback will receive as its first argument each of the elements in $items, - * the value returned by the callback will be used as the value for sorting such - * element. Please note that the callback function could be called more than once - * per element. - * - * ###Example: + * {@inheritDoc} * - * {{{ - * $items = $collection->sortBy(function ($user) { - * return $user->age; - * }); - * - * // alternatively - * $items = $collection->sortBy('age'); - * - * // or use a property path - * $items = $collection->sortBy('department.name'); - * - * // output all user name order by their age in descending order - * foreach ($items as $user) { - * echo $user->name; - * } - * }}} - * - * @param callable|string $callback the callback or column name to use for sorting - * @param int $dir either SORT_DESC or SORT_ASC - * @param int $type the type of comparison to perform, either SORT_STRING - * SORT_NUMERIC or SORT_NATURAL - * @return \Cake\Collection\Collection */ public function sortBy($callback, $dir = SORT_DESC, $type = SORT_NUMERIC) { return new Collection(new SortIterator($this, $callback, $dir, $type)); } /** - * Splits a collection into sets, grouped by the result of running each value - * through the callback. If $callback is is a string instead of a callable, - * groups by the property named by $callback on each of the values. - * - * When $callback is a string it should be a property name to extract or - * a dot separated path of properties that should be followed to get the last - * one in the path. - * - * ###Example: + * {@inheritDoc} * - * {{{ - * $items = [ - * ['id' => 1, 'name' => 'foo', 'parent_id' => 10], - * ['id' => 2, 'name' => 'bar', 'parent_id' => 11], - * ['id' => 3, 'name' => 'baz', 'parent_id' => 10], - * ]; - * - * $group = (new Collection($items))->groupBy('parent_id'); - * - * // Or - * $group = (new Collection($items))->groupBy(function ($e) { - * return $e['parent_id']; - * }); - * - * // Result will look like this when converted to array - * [ - * 10 => [ - * ['id' => 1, 'name' => 'foo', 'parent_id' => 10], - * ['id' => 3, 'name' => 'baz', 'parent_id' => 10], - * ], - * 11 => [ - * ['id' => 2, 'name' => 'bar', 'parent_id' => 11], - * ] - * ]; - * }}} - * - * @param callable|string $callback the callback or column name to use for grouping - * or a function returning the grouping key out of the provided element - * @return \Cake\Collection\Collection */ public function groupBy($callback) { $callback = $this->_propertyExtractor($callback); @@ -422,41 +180,8 @@ public function groupBy($callback) { } /** - * Given a list and a callback function that returns a key for each element - * in the list (or a property name), returns an object with an index of each item. - * Just like groupBy, but for when you know your keys are unique. - * - * When $callback is a string it should be a property name to extract or - * a dot separated path of properties that should be followed to get the last - * one in the path. + * {@inheritDoc} * - * ###Example: - * - * {{{ - * $items = [ - * ['id' => 1, 'name' => 'foo'], - * ['id' => 2, 'name' => 'bar'], - * ['id' => 3, 'name' => 'baz'], - * ]; - * - * $indexed = (new Collection($items))->indexBy('id'); - * - * // Or - * $indexed = (new Collection($items))->indexBy(function ($e) { - * return $e['id']; - * }); - * - * // Result will look like this when converted to array - * [ - * 1 => ['id' => 1, 'name' => 'foo'], - * 3 => ['id' => 3, 'name' => 'baz'], - * 2 => ['id' => 2, 'name' => 'bar'], - * ]; - * }}} - * - * @param callable|string $callback the callback or column name to use for indexing - * or a function returning the indexing key out of the provided element - * @return \Cake\Collection\Collection */ public function indexBy($callback) { $callback = $this->_propertyExtractor($callback); @@ -468,40 +193,8 @@ public function indexBy($callback) { } /** - * Sorts a list into groups and returns a count for the number of elements - * in each group. Similar to groupBy, but instead of returning a list of values, - * returns a count for the number of values in that group. - * - * When $callback is a string it should be a property name to extract or - * a dot separated path of properties that should be followed to get the last - * one in the path. + * {@inheritDoc} * - * ###Example: - * - * {{{ - * $items = [ - * ['id' => 1, 'name' => 'foo', 'parent_id' => 10], - * ['id' => 2, 'name' => 'bar', 'parent_id' => 11], - * ['id' => 3, 'name' => 'baz', 'parent_id' => 10], - * ]; - * - * $group = (new Collection($items))->countBy('parent_id'); - * - * // Or - * $group = (new Collection($items))->countBy(function ($e) { - * return $e['parent_id']; - * }); - * - * // Result will look like this when converted to array - * [ - * 10 => 2, - * 11 => 1 - * ]; - * }}} - * - * @param callable|string $callback the callback or column name to use for indexing - * or a function returning the indexing key out of the provided element - * @return \Cake\Collection\Collection */ public function countBy($callback) { $callback = $this->_propertyExtractor($callback); @@ -517,25 +210,8 @@ public function countBy($callback) { } /** - * Returns the total sum of all the values extracted with $matcher - * or of this collection. - * - * ###Example: + * {@inheritDoc} * - * {{{ - * $items = [ - * ['invoice' => ['total' => 100], - * ['invoice' => ['total' => 200] - * ]; - * - * $total = (new Collection($items))->sumOf('invoice.total'); - * - * // Total: 300 - * }}} - * - * @param string|callable $matcher The property name to sum or a function - * that will return the value of the property to sum. - * @return float|int */ public function sumOf($matcher) { $callback = $this->_propertyExtractor($matcher); @@ -548,10 +224,8 @@ public function sumOf($matcher) { } /** - * Returns a new collection with the elements placed in a random order, - * this function does not preserve the original keys in the collection. + * {@inheritDoc} * - * @return \Cake\Collection\Collection */ public function shuffle() { $elements = iterator_to_array($this); @@ -560,55 +234,24 @@ public function shuffle() { } /** - * Returns a new collection with maximum $size random elements - * from this collection + * {@inheritDoc} * - * @param int $size the maximum number of elements to randomly - * take from this collection - * @return \Cake\Collection\Collection */ public function sample($size = 10) { return new Collection(new LimitIterator($this->shuffle(), 0, $size)); } /** - * Returns a new collection with maximum $size elements in the internal - * order this collection was created. If a second parameter is passed, it - * will determine from what position to start taking elements. + * {@inheritDoc} * - * @param int $size the maximum number of elements to take from - * this collection - * @param int $from A positional offset from where to take the elements - * @return \Cake\Collection\Collection */ public function take($size = 1, $from = 0) { return new Collection(new LimitIterator($this, $from, $size)); } /** - * Looks through each value in the list, returning a Collection of all the - * values that contain all of the key-value pairs listed in $conditions. - * - * ###Example: - * - * {{{ - * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] - * ]; - * - * $extracted = (new Collection($items))->match(['user.name' => 'Renan']); + * {@inheritDoc} * - * // Result will look like this when converted to array - * [ - * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']] - * ] - * }}} - * - * @param array $conditions a key-value list of conditions where - * the key is a property path as accepted by `Collection::extract, - * and the value the condition against with each element will be matched - * @return \Cake\Collection\Collection */ public function match(array $conditions) { $matchers = []; @@ -630,23 +273,16 @@ public function match(array $conditions) { } /** - * Returns the first result matching all of the key-value pairs listed in - * conditions. + * {@inheritDoc} * - * @param array $conditions a key-value list of conditions where the key is - * a property path as accepted by `Collection::extract`, and the value the - * condition against with each element will be matched - * @see \Cake\Collection\Collection::match() - * @return mixed */ public function firstMatch(array $conditions) { return $this->match($conditions)->first(); } /** - * Returns the first result in this collection + * {@inheritDoc} * - * @return mixed The first value in the collection will be returned. */ public function first() { foreach ($this->take(1) as $result) { @@ -655,11 +291,8 @@ public function first() { } /** - * Returns a new collection as the result of concatenating the list of elements - * in this collection with the passed list of elements + * {@inheritDoc} * - * @param array|\Traversable $items Items list. - * @return \Cake\Collection\Collection */ public function append($items) { $list = new AppendIterator; @@ -669,44 +302,8 @@ public function append($items) { } /** - * Returns a new collection where the values extracted based on a value path - * and then indexed by a key path. Optionally this method can produce parent - * groups based on a group property path. - * - * ### Examples: - * - * {{{ - * $items = [ - * ['id' => 1, 'name' => 'foo', 'parent' => 'a'], - * ['id' => 2, 'name' => 'bar', 'parent' => 'b'], - * ['id' => 3, 'name' => 'baz', 'parent' => 'a'], - * ]; - * - * $combined = (new Collection($items))->combine('id', 'name'); - * - * // Result will look like this when converted to array - * [ - * 1 => 'foo', - * 2 => 'bar', - * 3 => 'baz', - * ]; + * {@inheritDoc} * - * $combined = (new Collection($items))->combine('id', 'name', 'parent'); - * - * // Result will look like this when converted to array - * [ - * 'a' => [1 => 'foo', 3 => 'baz'], - * 'b' => [2 => 'bar'] - * ]; - * }}} - * - * @param callable|string $keyPath the column name path to use for indexing - * or a function returning the indexing key out of the provided element - * @param callable|string $valuePath the column name path to use as the array value - * or a function returning the value out of the provided element - * @param callable|string $groupPath the column name path to use as the parent - * grouping key or a function returning the key out of the provided element - * @return \Cake\Collection\Collection */ public function combine($keyPath, $valuePath, $groupPath = null) { $options = [ @@ -743,14 +340,8 @@ public function combine($keyPath, $valuePath, $groupPath = null) { } /** - * Returns a new collection where the values are nested in a tree-like structure - * based on an id property path and a parent id property path. + * {@inheritDoc} * - * @param callable|string $idPath the column name path to use for determining - * 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 - * @return \Cake\Collection\Collection */ public function nest($idPath, $parentPath) { $parents = []; @@ -791,39 +382,8 @@ public function nest($idPath, $parentPath) { } /** - * Returns a new collection containing each of the elements found in `$values` as - * a property inside the corresponding elements in this collection. The property - * where the values will be inserted is described by the `$path` parameter. - * - * The $path can be a string with a property name or a dot separated path of - * properties that should be followed to get the last one in the path. - * - * If a column or property could not be found for a particular element in the - * collection as part of the path, the element will be kept unchanged. - * - * ### Example: - * - * Insert ages into a collection containing users: + * {@inheritDoc} * - * {{{ - * $items = [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']], - * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan']] - * ]; - * $ages = [25, 28]; - * $inserted = (new Collection($items))->insert('comment.user.age', $ages); - * - * // Result will look like this when converted to array - * [ - * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark', 'age' => 25]], - * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan', 'age' => 28]] - * ]; - * }}} - * - * @param string $path a dot separated string symbolizing the path to follow - * inside the hierarchy of each value so that the value can be inserted - * @param mixed $values The values to be inserted at the specified path, - * values are matched with the elements in this collection by its positional index. * @return \Cake\Collection\Iterator\InsertIterator */ public function insert($path, $values) { @@ -831,71 +391,31 @@ public function insert($path, $values) { } /** - * Returns an array representation of the results + * {@inheritDoc} * - * @param bool $preserveKeys whether to use the keys returned by this - * collection as the array keys. Keep in mind that it is valid for iterators - * to return the same key for different elements, setting this value to false - * can help getting all items if keys are not important in the result. - * @return array */ public function toArray($preserveKeys = true) { return iterator_to_array($this, $preserveKeys); } /** - * Convert a result set into JSON. - * - * Part of JsonSerializable interface. + * {@inheritDoc} * - * @return array The data to convert to JSON */ public function jsonSerialize() { return $this->toArray(); } /** - * Iterates once all elements in this collection and executes all stacked - * operations of them, finally it returns a new collection with the result. - * This is useful for converting non-rewindable internal iterators into - * a collection that can be rewound and used multiple times. - * - * A common use case is to re-use the same variable for calculating different - * data. In those cases it may be helpful and more performant to first compile - * a collection and then apply more operations to it. - * - * ### Example: + * {@inheritDoc} * - * {{{ - * $collection->map($mapper)->sortBy('age')->extract('name'); - * $compiled = $collection->compile(); - * $isJohnHere = $compiled->some($johnMatcher); - * $allButJohn = $compiled->filter($johnMatcher); - * }}} - * - * In the above example, had the collection not been compiled before, the - * iterations for `map`, `sortBy` and `extract` would've been executed twice: - * once for getting `$isJohnHere` and once for `$allButJohn` - * - * You can think of this method as a way to create save points for complex - * calculations in a collection. - * - * @param bool $preserveKeys whether to use the keys returned by this - * collection as the array keys. Keep in mind that it is valid for iterators - * to return the same key for different elements, setting this value to false - * can help getting all items if keys are not important in the result. - * @return \Cake\Collection\Collection */ public function compile($preserveKeys = true) { return new Collection($this->toArray($preserveKeys)); } /** - * Returns a new collection where the operations performed by this collection. - * No matter how many times the new collection is iterated, those operations will - * only be performed once. - * - * This can also be used to make any non-rewindable iterator rewindable. + * {@inheritDoc} * * @return \Cake\Collection\Iterator\BufferedIterator */ @@ -904,42 +424,8 @@ public function buffered() { } /** - * Returns a new collection with each of the elements of this collection - * after flattening the tree structure. The tree structure is defined - * by nesting elements under a key with a known name. It is possible - * to specify such name by using the '$nestingKey' parameter. - * - * By default all elements in the tree following a Depth First Search - * will be returned, that is, elements from the top parent to the leaves - * for each branch. - * - * It is possible to return all elements from bottom to top using a Breadth First - * Search approach by passing the '$dir' parameter with 'asc'. That is, it will - * return all elements for the same tree depth first and from bottom to top. - * - * Finally, you can specify to only get a collection with the leaf nodes in the - * tree structure. You do so by passing 'leaves' in the first argument. - * - * The possible values for the first argument are aliases for the following - * constants and it is valid to pass those instead of the alias: - * - * - desc: TreeIterator::SELF_FIRST - * - asc: TreeIterator::CHILD_FIRST - * - leaves: TreeIterator::LEAVES_ONLY - * - * ### Example: - * - * {{{ - * $collection = new Collection([ - * ['id' => 1, 'children' => [['id' => 2, 'children' => [['id' => 3]]]]], - * ['id' => 4, 'children' => [['id' => 5]]] - * ]); - * $flattenedIds = $collection->listNested()->extract('id'); // Yields [1, 2, 3, 4, 5] - * }}} + * {@inheritDoc} * - * @param string|int $dir The direction in which to return the elements - * @param string|callable $nestingKey The key name under which children are nested - * or a callable function that will return the children list * @return \Cake\Collection\Iterator\TreeIterator */ public function listNested($dir = 'desc', $nestingKey = 'children') {