Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Baum Laravel Package

ciaochung/baum

Baum provides nested set model support for Laravel Eloquent models, letting you store and query hierarchical tree data such as categories or menus with efficient ancestor, descendant, sibling, and depth operations.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the package is installable first. With 0 stars and 0 dependents this is a personal fork of baum/baum, so before writing any code:

composer show ciaochung/baum --all   # is it on Packagist?

If it isn't published, add it as a VCS repository in composer.json:

"repositories": [{ "type": "vcs", "url": "https://github.com/ciao-chung/baum" }],
"require": { "ciaochung/baum": "dev-master" }

Then check composer.json inside the package for its illuminate/* constraint — Baum's classic API (Baum\Node extends Illuminate\Database\Eloquent\Model) was written for Laravel 5.x. Verify it boots on your Laravel version with a throwaway test before adopting it.

Migration. Three required columns plus the parent pointer (note lft/rgt, not left/right — those are MySQL reserved words):

Schema::create('categories', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->unsignedInteger('parent_id')->nullable();
    $table->unsignedInteger('lft')->nullable()->index();
    $table->unsignedInteger('rgt')->nullable()->index();
    $table->unsignedInteger('depth')->nullable();
    $table->timestamps();
});

Model. Extend Baum\Node instead of Model:

use Baum\Node;

class Category extends Node
{
    protected $table = 'categories';
    protected $fillable = ['name'];
    // defaults: parent_id / lft / rgt / depth — override only if your schema differs
    protected $parentColumn = 'parent_id';
    protected $leftColumn   = 'lft';
    protected $rightColumn  = 'rgt';
    protected $depthColumn  = 'depth';
    protected $orderColumn  = null;
}

First use case — build a tree in a seeder and read it back:

$root     = (new Category(['name' => 'Root']))->saveAsRoot();
$hardware = (new Category(['name' => 'Hardware']))->makeChildOf($root);
$laptops  = (new Category(['name' => 'Laptops']))->makeChildOf($hardware);

Category::getNestedList('name');      // [1 => 'Root', 2 => '  Hardware', 3 => '    Laptops']
$laptops->getAncestors();             // Root + Hardware, one query
$hardware->getDescendants();          // Laptops, one query
$laptops->getRoot()->name;            // 'Root'

Where to look first in the repo: src/Baum/Node.php (moves, relations, scopes) and src/Baum/Move.php (the actual lft/rgt arithmetic). If the tree ever gets corrupted, that's the file that explains why.

Implementation Patterns

Always mutate the tree through API methods, never by assignment. $node->parent_id = 5; $node->save(); will silently corrupt lft/rgt for the whole subtree. Use:

$node->makeChildOf($parent);       // reparent
$node->makeSiblingOf($other);      // move next to
$node->moveToLeftOf($other);
$node->moveToRightOf($other);
$node->moveToLeft();               // swap with previous sibling
$node->moveToRight();

Rendering a menu — two idiomatic options.

Recursive Blade partial (best when you need links, icons, per-node logic):

// Category.php
public function children() { return $this->hasMany(Category::class, 'parent_id'); }

// Blade
@foreach ($categories as $category)
    @include('partials.category-node', ['category' => $category])
@endforeach

with @foreach ($category->children as $child) @include(...) @endforeach — eager load with Category::with('children')->whereNull('parent_id')->get() to avoid N+1.

Flat list (best for <select> and admin lists):

Category::getNestedList('name', 'id', '— ');   // [id => '— — Laptops']

Scoped trees (the killer feature for multi-tenancy / per-project categories). Declare the scope columns and Baum keeps each tree independent automatically:

protected $scoped = ['company_id'];

Category::where('company_id', $id)->first()->getDescendants();  // stays inside the scope

Every query on a scoped model must carry the scope, otherwise sibling trees look merged.

Subtree moves for reordering, not per-node loops. A reorder drag-and-drop usually means one makeChildOf() per drop, which is fine — each is a bounded set of UPDATEs. Avoid rebuilding the entire tree on every save (see Gotchas).

Deletes: handle descendants yourself.

DB::transaction(function () use ($category) {
    $category->getDescendants()->each->delete();   // leaves first
    $category->delete();
});

Or re-parent children to the grandparent with $child->makeChildOf($category->parent) before deleting.

Composing with other relations. getDescendants() returns a normal Eloquent Collection, so pluck IDs for a second query instead of looping:

$ids = $category->getDescendantsAndSelf()->pluck('id');
Product::whereIn('category_id', $ids)->paginate(25);

Scoping helpers are handy in admin screens:

Category::withoutNode($node)->get();       // candidates for "move to…"
Category::withoutRoot()->get();
Category::withoutSelf()->get();
$node->getImmediateDescendants();          // direct children only
$node->getSiblings();

Gotchas and Tips

  • Maintenance risk is the headline gotcha. Zero stars, zero dependents, a fork of an already-dormant package. Budget a half-day to verify it boots on your Laravel version, and keep kalnoy/nestedset in your back pocket as the maintained alternative with a very similar API. Pin the commit hash (dev-master#<sha>) rather than a floating branch.
  • lft/rgt naming is deliberate — MySQL reserves left/right. Don't "clean up" the column names unless you also override $leftColumn/$rightColumn.
  • Writes are O(subtree), not O(1). A move rewrites the boundary columns of every row in the affected subtrees. Wrap multi-step reorders in a transaction and never move nodes inside a large foreach without measuring.
  • Nested sets are not concurrency-safe. Two simultaneous writes can interleave the UPDATEs and shred the tree. Serialize structural writes (queue, lock, or SELECT … FOR UPDATE on the root row) if your admin UI can be used by more than one person.
  • Mass assignment of parent_id — if parent_id is in $fillable, a stray Category::create(['parent_id' => …]) bypasses the move logic. Keep it out of $fillable and set it only via makeChildOf().
  • Deleting a parent does not re-parent its children in any version of Baum I'd trust here. Test your delete path explicitly; the classic symptom is a tree where getDescendants() returns nothing for a node that visibly has children.
  • Indices matter. Add composite indexes on (lft, rgt) and (parent_id); descendant queries are range scans on lft BETWEEN … AND …, and without the index a large catalog will crawl.
  • Debugging a corrupted tree: read the raw columns. lft/rgt should form properly nested intervals (child's lft > parent's lft, child's rgt < parent's rgt, depth = ancestor count). If a rebuild helper ships in this fork, verify it against a backup first — rebuilding is itself destructive if the parent pointers are wrong.
  • Extension point: the cleanest way to add behaviour (slugs per level, path caching, audit logging) is a moving/saved observer or a small subclass of Baum\Node, not patches to Move.php. Override getNestedList() for custom labels rather than post-processing the array.
  • Testing: SQLite handles the integer columns fine, but assert on lft/rgt after every move type — that's where forks diverge. A quick property test ("for every node, lft < rgt and intervals nest") catches regressions fast.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core