diff --git a/README.md b/README.md index f28fd10..36221c2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Supports Laravel 5.5.29+. - [Depth](#depth) - [Path](#path) - [Custom Paths](#custom-paths) +- [Nested Results](#nested-results) ### Getting Started @@ -328,6 +329,50 @@ echo $descendantsAndSelf[1]->slug_path; // user-1/user-2 echo $descendantsAndSelf[2]->slug_path; // user-1/user-2/user-3 ``` +# Nested Results + +Use the `toTree()` method on the result collection to generate a nested tree: + +```php +$users = User::tree()->get(); + +$tree = $users->toTree(); +``` + +This recursively sets `children` and `parent` relationships: + +```json +[ + { + "id": 1, + "children": [ + { + "id": 2, + "children": [ + { + "id": 4, + "children": [], + "parent": { + "id": 2 + } + } + ], + "parent": { + "id": 1 + } + }, + { + "id": 3, + "children": [], + "parent": { + "id": 1 + } + } + ] + } +] +``` + ## Contributing Please see [CONTRIBUTING](.github/CONTRIBUTING.md) and [CODE OF CONDUCT](.github/CODE_OF_CONDUCT.md) for details. diff --git a/src/Eloquent/Collection.php b/src/Eloquent/Collection.php new file mode 100644 index 0000000..6563d1b --- /dev/null +++ b/src/Eloquent/Collection.php @@ -0,0 +1,38 @@ +isEmpty()) { + return $this; + } + + $parentKeyName = $this->first()->getParentKeyName(); + $localKeyName = $this->first()->getLocalKeyName(); + $depthName = $this->first()->getDepthName(); + + $depths = $this->pluck($depthName); + + $tree = new static( + $this->where($depthName, $depths->min())->values() + ); + + $itemsByParentKey = $this->groupBy($parentKeyName); + $itemsByLocalKey = $this->keyBy($localKeyName); + + foreach ($this->items as $item) { + $item->setRelation($childrenRelation, $itemsByParentKey[$item->$localKeyName] ?? new static()); + + if (isset($item->$parentKeyName, $itemsByLocalKey[$item->$parentKeyName])) { + $item->setRelation($parentRelation, $itemsByLocalKey[$item->$parentKeyName]); + } + } + + return $tree; + } +} diff --git a/src/Eloquent/HasRecursiveRelationships.php b/src/Eloquent/HasRecursiveRelationships.php index 98f6b0f..9c44465 100644 --- a/src/Eloquent/HasRecursiveRelationships.php +++ b/src/Eloquent/HasRecursiveRelationships.php @@ -416,4 +416,15 @@ public function newEloquentBuilder($query) { return new \Staudenmeir\LaravelAdjacencyList\Eloquent\Builder($query); } + + /** + * Create a new Eloquent Collection instance. + * + * @param array $models + * @return \Staudenmeir\LaravelAdjacencyList\Eloquent\Collection + */ + public function newCollection(array $models = []) + { + return new Collection($models); + } } diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php new file mode 100644 index 0000000..cf2a11a --- /dev/null +++ b/tests/CollectionTest.php @@ -0,0 +1,47 @@ +orderBy('id')->get(); + + $tree = $users->toTree(); + + $this->assertEquals([1, 11], $tree->pluck('id')->all()); + $this->assertFalse($tree[0]->relationLoaded('parent')); + $this->assertEquals([2, 3, 4], $tree[0]->children->pluck('id')->all()); + $this->assertTrue($tree[0]->children[0]->relationLoaded('parent')); + $this->assertEquals(1, $tree[0]->children[0]->parent->id); + $this->assertEquals([5], $tree[0]->children[0]->children->pluck('id')->all()); + $this->assertEquals([8], $tree[0]->children[0]->children[0]->children->pluck('id')->all()); + $this->assertEquals([12], $tree[1]->children->pluck('id')->all()); + } + + public function testToTreeWithDescendants() + { + $users = User::find(1)->descendants()->orderBy('id')->get(); + + $tree = $users->toTree(); + + $this->assertEquals([2, 3, 4], $tree->pluck('id')->all()); + $this->assertFalse($tree[0]->relationLoaded('parent')); + $this->assertEquals([5], $tree[0]->children->pluck('id')->all()); + $this->assertTrue($tree[0]->children[0]->relationLoaded('parent')); + $this->assertEquals(2, $tree[0]->children[0]->parent->id); + $this->assertEquals([8], $tree[0]->children[0]->children->pluck('id')->all()); + } + + public function testToTreeWithEmptyCollection() + { + $users = User::tree(1)->where('id', 0)->get(); + + $tree = $users->toTree(); + + $this->assertEmpty($tree); + } +}