Installation:
composer require vusys/laravel-nestedset
Publish the migration (if using the built-in schema):
php artisan vendor:publish --provider="Vusys\NestedSet\NestedSetServiceProvider" --tag="migrations"
php artisan migrate
Model Integration:
Use the HasNestedSet trait in your Eloquent model:
use Vusys\NestedSet\HasNestedSet;
class Category extends Model
{
use HasNestedSet;
protected $fillable = ['name', 'lft', 'rgt', 'depth'];
}
First Use Case: Create a root node:
$root = Category::create(['name' => 'Root']);
$root->save(); // Automatically sets lft/rgt/depth
Insert a child:
$child = $root->appendChild(['name' => 'Child']);
$child->save();
HasNestedSet trait – Core methods.NestedSetServiceProvider – Configuration.Hierarchy Management:
// Append as last child
$parent->appendChild(['name' => 'New Child']);
// Insert at specific position (e.g., 2nd child)
$parent->insertChild(2, ['name' => 'Middle Child']);
$node->moveTo($newParent, 'last-child'); // or 'first-child', 'before', 'after'
Querying:
$children = Category::whereParentId($parentId)->get();
$shallowNodes = Category::whereDepth('<', 3)->get();
$path = $node->getPath(); // Array of ancestors
Bulk Operations:
$subtree = Category::whereIn('id', [$id1, $id2])->reorder();
Event Hooks:
nestedset.saving, nestedset.saved, etc.:
Category::saved(function ($model) {
// Post-save logic (e.g., cache updates)
});
getLeftAttribute(), getRightAttribute(), etc., if using non-standard column names.deleted_at:
use Illuminate\Database\Eloquent\SoftDeletes;
class Category extends Model
{
use HasNestedSet, SoftDeletes;
}
public function getChildrenAttribute()
{
return $this->children()->get();
}
Database Locks:
reorder()) may cause locks. Use transactions:
DB::transaction(function () {
$node->moveTo($parent, 'last-child');
});
Circular References:
if ($node->isDescendantOf($newParent)) {
throw new \Exception("Cannot move node under itself.");
}
Performance:
getDescendants() on deep trees in loops. Use cursors or pagination:
$descendants = $node->descendants()->cursor();
Migration Conflicts:
lft, rgt, depth columns with unsignedBigInteger type.Validate lft/rgt:
Check for gaps or overlaps with:
SELECT lft, rgt, id FROM categories ORDER BY lft;
Use php artisan nestedset:repair if corrupted.
Log Events:
Enable debug logging for nested-set events in config/nestedset.php:
'debug' => env('NESTEDSET_DEBUG', false),
Custom Storage:
Override getLeftAttribute() to use a different column:
protected function getLeftAttribute($value)
{
return $this->{$this->getLeftColumn()} ?? $value;
}
Hooks: Extend the trait to add pre/post-save logic:
protected static function bootHasNestedSet()
{
static::saved(function ($model) {
// Custom logic
});
}
Query Scopes:
Add custom scopes to HasNestedSet:
public function scopeActive($query)
{
return $query->where('active', true);
}
Default Order:
The package assumes lft/rgt are unsignedBigInteger. Change in config/nestedset.php if needed:
'columns' => [
'left' => 'lft',
'right' => 'rgt',
'depth' => 'depth',
],
Tree Depth Limit:
Default depth column is tinyInteger. Increase to smallInteger for >127 levels:
Schema::table('categories', function (Blueprint $table) {
$table->smallInteger('depth')->unsigned()->default(0);
});
How can I help you explore Laravel packages today?