Installation:
composer require gzero/eloquent-tree
Ensure your Laravel version (5.3+) matches the package's requirements.
Model Setup:
Extend the base Tree model in your Eloquent model:
use Gzero\EloquentTree\Model\Tree;
class Category extends Tree
{
protected $table = 'categories';
// Custom fields, relations, etc.
}
Migration: Run the provided migration (adjust table/column names as needed):
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->integer('parent_id')->unsigned()->nullable();
$table->integer('lft')->unsigned();
$table->integer('rgt')->unsigned();
$table->integer('depth')->unsigned();
$table->timestamps();
});
First Use Case: Create a root node:
$root = Category::create(['name' => 'Electronics']);
Add a child:
$child = $root->children()->create(['name' => 'Phones']);
Tree Traversal:
$root->descendants; // Collection of all children, grandchildren, etc.
$root->children; // Collection of immediate children
$child->ancestors; // Collection from child → root
Node Manipulation:
$node->move($newParent); // Moves entire subtree under $newParent
$node->insertSibling(['name' => 'New Sibling'], 'after'); // 'before'|'after'
Dynamic Tree Building:
$tree = Category::buildTree($flatData, 'id', 'parent_id');
Query Scoping:
Category::whereIn('id', $root->descendants->pluck('id'));
Polymorphic Trees:
Use morphTo/morphWith for shared tree structures (e.g., categories for products/media):
public function treeable()
{
return $this->morphTo();
}
Update the Tree model to handle polymorphic relations.
Caching: Cache tree structures for read-heavy apps:
$tree = Cache::remember("tree_{$root->id}", now()->addHours(1), function() use ($root) {
return $root->withDescendants;
});
Frontend Rendering: Serialize trees for JavaScript (e.g., nested-sortable):
$treeData = $root->toTreeArray(); // Custom method to flatten for JS
Database Constraints:
parent_id is nullable (roots have NULL).$table->foreign('parent_id')->references('id')->on('categories')->onDelete('cascade');
Performance:
descendants on deep trees (>1000 nodes). Use pagination or lazy loading:
$root->descendants()->paginate(50);
lft, rgt, and depth columns for large trees.Concurrency:
move) are atomic but may lock tables. Test under load.Legacy Data:
Category::rebuildTree(); // Built-in method to recalculate lft/rgt/depth
Tree Corruption:
If lft/rgt values are inconsistent, run:
$node->rebuildTree(); // Recalculates for a subtree
Or globally:
Category::rebuildTree();
Query Debugging: Enable Eloquent logging:
\DB::enableQueryLog();
$tree = Category::find(1)->withDescendants;
\DB::getQueryLog(); // Inspect generated SQL
Custom Scopes: Add reusable scopes to your model:
public function scopeActive($query)
{
return $query->where('active', true);
}
Usage:
Category::active()->whereHasDescendants();
Event Hooks:
Listen for tree events (e.g., tree.moved):
Category::moved(function ($model) {
// Log or notify when a node is moved
});
Custom Tree Builders:
Override buildTree() for non-standard hierarchies:
public static function buildTree(array $items, $idKey = 'id', $parentKey = 'parent_id')
{
// Custom logic (e.g., handle circular references)
}
Soft Deletes: Extend for soft deletes (Laravel 5.7+):
use SoftDeletes;
class Category extends Tree {
use SoftDeletes;
// ...
}
Update queries to handle soft-deleted nodes in trees.
Bulk Operations:
Use insertSibling in loops for batch creation:
$parent = Category::find(1);
foreach ($items as $item) {
$parent->insertSibling($item, 'after');
}
Tree Depth Limits: Enforce max depth in validation:
public function validateDepth()
{
$this->validate(['depth' => 'max:5']); // Prevent infinite trees
}
Multi-Tenant Trees: Scope trees by tenant:
class Category extends Tree {
public function scopeForTenant($query, $tenantId)
{
return $query->where('tenant_id', $tenantId);
}
}
How can I help you explore Laravel packages today?