Installation
composer require typicms/nestablecollection
Ensure your project meets the requirements (PHP ^8.3, Laravel 12/13).
Model Setup
Add parent_id to $fillable and use the NestableTrait:
use TypiCMS\NestableTrait;
class Category extends Model
{
use NestableTrait;
protected $fillable = ['parent_id', /* ... */];
}
First Use Case Fetch a collection of nested items (e.g., categories, menus, or hierarchical data):
$categories = Category::all(); // Returns NestableCollection
$categories->toTree(); // Converts to a nested array
Building Hierarchies Convert flat adjacency-list data to nested structures:
$tree = Category::all()->toTree();
// Returns an array like:
// [
// 'id' => 1,
// 'name' => 'Root',
// 'children' => [...]
// ]
Querying Nested Data
Use whereHasNested() to filter nested relationships:
$categories = Category::whereHasNested('children', function ($query) {
$query->where('name', 'like', '%Electronics%');
})->get();
Recursive Operations Traverse or modify nested structures:
$categories->eachRecursive(function ($item) {
// Process each node (including children)
});
Dynamic Sorting Sort nested collections by custom logic:
$sorted = Category::all()->sortNested(function ($a, $b) {
return $a->name <=> $b->name;
});
API Responses Serialize nested data directly:
return response()->json($categories->toTree());
Blade Views
Render nested structures with @foreach:
@foreach($categories->toTree() as $category)
<div>{{ $category['name'] }}</div>
@include('partials.nested', ['items' => $category['children']])
@endforeach
Caching Cache nested collections to avoid repeated queries:
$tree = Cache::remember('category-tree', now()->addHours(1), function () {
return Category::all()->toTree();
});
Performance with Large Trees
toTree() and recursive methods can be memory-intensive for deep hierarchies.limitDepth() to cap recursion:
$categories->toTree()->limitDepth(3);
Circular References
parent_id references itself (e.g., parent_id = id), the collection may loop infinitely.ignoreSelfReferences():
$categories->toTree()->ignoreSelfReferences();
Query Scope Conflicts
globalScope) may interfere with nested queries.$categories = Category::withoutGlobalScopes(function () {
return Category::whereHasNested(...)->get();
});
Inspect Raw Data
Use getAdjacencyList() to debug flat structures:
$flatData = $categories->getAdjacencyList();
Log Recursive Errors
Wrap recursive operations in try-catch:
try {
$categories->eachRecursive(...);
} catch (\Exception $e) {
Log::error('Nested traversal failed', ['error' => $e->getMessage()]);
}
Custom Tree Formats
Override toTree() in your model:
public function toCustomTree()
{
return $this->toTree(function ($item) {
return [
'id' => $item['id'],
'slug' => Str::slug($item['name']),
'children' => $item['children'] ?? []
];
});
}
Add Nesting Logic Extend the trait for domain-specific rules:
use TypiCMS\NestableTrait;
class Category extends Model
{
use NestableTrait;
public function validateNesting($parent, $child)
{
// Custom validation (e.g., prevent siblings with same name)
return true;
}
}
Performance Optimizations
cursor() for large datasets:
$categories = Category::cursor()->toTree();
parent_id is indexed for faster queries.How can I help you explore Laravel packages today?