Installation:
composer require gazsp/baum
Add the trait to your Eloquent model:
use Gazsp\Baum\NodeTrait;
Database Migration:
Add lft and rgt columns to your table:
Schema::table('categories', function (Blueprint $table) {
$table->integer('lft')->unsigned();
$table->integer('rgt')->unsigned();
});
First Use Case:
// Create a root node
$root = Category::create(['name' => 'Root']);
$root->save(); // Automatically sets lft/rgt
// Create a child
$child = $root->appendChild(['name' => 'Child']);
$child->save();
Tree traversal:
$root->children; // All direct children
$root->descendants; // All descendants (recursive)
$root->ancestors; // All ancestors
Positioning:
$node->moveBefore($sibling); // Reorders nodes
Building Hierarchies:
// Create a multi-level tree
$root = Category::create(['name' => 'Root']);
$level1 = $root->appendChild(['name' => 'Level 1']);
$level2 = $level1->appendChild(['name' => 'Level 2']);
$level2->save(); // Saves entire subtree
Querying:
// Find all nodes under a specific path
$subtree = Category::where('lft', '>=', $parent->lft)
->where('rgt', '<=', $parent->rgt)
->get();
// Find siblings
$siblings = $parent->parent->children;
Bulk Operations:
// Reorder children
$parent->children->sortBy('name')->each->save();
Scopes:
// Add a scope to filter by depth
public function scopeAtDepth($query, $depth) {
return $query->where('lft', '>=', $depth * 2)
->where('rgt', '<=', ($depth + 1) * 2 - 1);
}
Events:
// Listen for tree changes
Category::saved(function ($model) {
if ($model->wasChanged('lft') || $model->wasChanged('rgt')) {
// Handle tree reordering
}
});
API Responses:
// Serialize tree structure
$tree = $root->toTreeArray(); // Returns nested array
Concurrency Issues:
lft/rgt for ordering. Concurrent save() calls on siblings may corrupt the tree.DB::transaction(function () use ($node) {
$node->moveBefore($sibling);
});
Performance with Large Trees:
save() due to recursive calculations. Baum::rebuild() sparingly.Circular References:
node->appendChild($node)) will fail silently.if ($node->isDescendantOf($child)) {
throw new \Exception("Cannot create circular reference");
}
Verify Tree Integrity:
// Check if lft/rgt values are valid
if ($node->lft > $node->rgt) {
throw new \Exception("Invalid tree structure");
}
Log Tree Changes:
// Override save to log changes
public function save(array $options = []) {
$this->fireModelEvent('saving', false);
$originalLft = $this->getOriginal('lft');
$originalRgt = $this->getOriginal('rgt');
$result = parent::save($options);
if ($originalLft !== $this->lft || $originalRgt !== $this->rgt) {
\Log::info("Tree reordered: {$this->name} (lft: {$this->lft}, rgt: {$this->rgt})");
}
return $result;
}
Custom Column Names:
lft/rgt columns:
class Category extends Model {
use NodeTrait;
protected $baum = [
'columns' => ['left' => 'lft_col', 'right' => 'rgt_col']
];
}
Root Node Handling:
// In a model observer
Category::creating(function ($model) {
if (!Category::whereNull('parent_id')->exists()) {
$model->parent_id = null;
}
});
Custom Tree Calculations:
getDepth() or getLevel() for custom logic:
public function getDepth() {
return $this->parent ? $this->parent->depth + 1 : 0;
}
Hooks for Tree Operations:
Gazsp\Baum\Node to add pre/post hooks:
class CustomNode extends \Gazsp\Baum\Node {
public function beforeMove() {
// Custom logic before moving
}
}
Alternative Storage:
TreeBuilder:
class CustomTreeBuilder extends \Gazsp\Baum\TreeBuilder {
public function build($model) {
// Custom tree-building logic
}
}
How can I help you explore Laravel packages today?