In-memory Tree Shaping

When you've already fetched a flat result, build the tree without extra queries. Every get() on a NodeTrait model returns a NodeCollection, which knows how to assemble itself.

Tip

If you only need to visit each node — not assemble nested children arrays — see Walking Subtrees. Walking is purely in-memory and skips the children-array allocation that toTree() builds.

1. The flat → tree transform

$flat = Category::query()->defaultOrder()->get();
// → Collection of 9 categories ordered by lft:
//   Electronics, Computers, Laptops, Desktops, Phones, Android,
//   Books, Fiction, Non-fiction

$tree = $flat->toTree();

$tree is a collection of the top-level nodes only, each with its children relation populated recursively:

$tree->count();                                // 2 — Electronics, Books
$tree[0]->name;                                // 'Electronics'
$tree[0]->children->count();                   // 2 — Computers, Phones
$tree[0]->children[0]->children->count();      // 2 — Laptops, Desktops

No extra queries fire — toTree() walks the already-loaded collection and rewires the relations by matching each node's parent_id to the keys present in the collection.

2. Flat-with-hierarchy

toFlatTree() returns a single flat collection in depth-first order — handy when you want to render a tree with <ul>-style indenting but don't want to recurse. It only orders the rows; it does not populate the parent / children relations. If you need those on the flat collection (to call ->parent / ->children without lazy loading), call linkNodes() as shown below:

foreach ($flat->toFlatTree() as $node) {
    echo str_repeat('  ', $node->depth) . $node->name . "\n";
}
// Electronics
//   Computers
//     Laptops
//     Desktops
//   Phones
//     Android
// Books
//   Fiction
//   Non-fiction

3. Subtree shaping

Both toTree() and toFlatTree() accept an optional $root argument when your collection is a subtree, not the whole table. Without it, every node whose parent isn't present in the collection becomes a top-level node — so a partial or filtered fetch returns a forest of all the maximal subtrees it contains, never silently dropping a node whose parent was filtered out. Pass $root to narrow the result to that node's subtree.

// Just Electronics + descendants, shaped as a subtree
$subtree = Category::query()
    ->whereDescendantOrSelf($electronics->getBounds())
    ->defaultOrder()
    ->get()
    ->toTree($electronics);

4. Linking nodes without restructuring

If you want the relations populated on the original flat collection — e.g. so each node can call ->parent or ->children without lazy loading — but don't want to re-shape into a tree, call linkNodes():

$flat->linkNodes();
$flat[3]->parent->name;                  // resolved without a query
$flat[0]->children->pluck('name');       // ['Computers', 'Phones']

toTree() calls linkNodes() internally, so the relations are already populated on the tree result too.