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

Nestedset Laravel Package

lunarphp/nestedset

A Laravel package for working with nested set trees. Store and query hierarchical data such as categories, menus, and org structures efficiently, with support for creating, moving, and reordering nodes using left and right indexes. Ideal for fast ancestry and descendant lookups.

View on GitHub
Deep Wiki
Context7

Getting Started

This is Lunar's fork of kalnoy/nestedset — a nested set implementation for Eloquent models (adjacency list parent_id plus _lft/_rgt bounds). It gives you fast, single-query subtree reads and cheap reordering, at the cost of writes that touch multiple rows. If you already know kalnoy/nestedset, the API is identical; only the namespace, PHP/Laravel floors, and some hardening have changed.

  1. composer require lunarphp/nestedset — note the hard requirement of Laravel 12/13. If you're on Laravel 10 or 11, stay on kalnoy/nestedset; this fork will not install.
  2. Publish/author the migration. The package ships a schema helper rather than an opinionated migration — add parent_id (nullable FK/index), _lft and _rgt (unsigned int, indexed), and optionally depth and a scope column, then index (_lft, _rgt).
  3. Add the trait and configure columns on the model:
    use Kalnoy\Nestedset\NodeTrait; // verify the namespace after install
    protected $fillable = ['name', 'parent_id'];
    
    public function getLftName() { return '_lft'; }
    public function getRgtName() { return '_rgt'; }
    
  4. First use case: a category/menu tree. Seed a root, then $child->appendToNode($root)->save(); and read back with Category::ancestorsOf($id)->get() or Category::descendantsOf($id)->get().

The README is the primary reference; the NodeTrait source is small and worth skimming once so you know exactly which queries each method runs.

Implementation Patterns

Creation and placement. Never set parent_id and save. Use the node API: appendToNode, prependToNode, insertBeforeNode, insertAfterNode, then save(). For bulk seeding, wrap in a transaction and consider saveQuietly() so tree events don't cascade.

Reading trees efficiently. descendantsOf($id) returns a query builder — constrained to the node's bounds — so you can chain ->where('active', 1) and still get correct subtree semantics in one query. ancestorsOf walks outward; siblingsAndSelf, children, leaves, root cover the rest. Use toTree() / toFlatTree() to hydrate an array-of-arrays in one hit rather than lazy-loading children in a loop (which is the classic N+1 in tree UIs).

Moving and reordering. moveTo, moveToStart, moveToEnd, moveToSibling, moveBefore, moveAfter. Each is a multi-row UPDATE on _lft/_rgt — always inside DB::transaction() when the move is part of a larger operation (e.g. drag-and-drop reorder plus an audit log).

Scoped trees. For multi-tenant or per-site trees, override getScopeAttributes() to return ['site_id' => ...] and index accordingly. Every query the trait builds is automatically constrained, so you don't have to remember where('site_id', ...) on relationship methods.

Deletion semantics. Default behaviour promotes children to the deleted node's parent (delete() repairs bounds). Call forceDelete() to take the whole subtree, or deleteDescendants() for a subtree prune. Soft-deleting a model without repairing the tree leaves holes — see gotchas.

API/consumer layers. Order by _lft for display, and use descendantsAndSelf when you need the node in the same result set. Depth is often the cheapest way to render indentation or limit nesting in validation ($node->depth).

Gotchas and Tips

  • Global scopes are the sharpest edge, and this release touches them. PR #3 hardens tree operations against global scopes — a real fix, because a SoftDeletes or tenant global scope silently altering the rows a moveTo/delete repair query sees corrupts left/right bounds. Test move and delete paths specifically if you have SoftDeletes, ActiveScope, or a tenant scope on the model.
  • Zero-offset SQL fix (PR #3). If you previously saw off-by-one bounds or _lft = 0 rows after reorders, that's the bug this addresses. Run a full rebuild of existing production trees after upgrading rather than assuming old data is clean.
  • Laravel 12/13 only. The modernised release uses native types and drops older framework support. A composer conflict error here is a version floor, not a bug.
  • Never mix raw SQL writes with the trait. Updates to parent_id or _lft/_rgt outside the API desynchronise the tree. If you must, follow with a rebuild and verify with the package's check/repair commands (list them with php artisan list after install — command names are worth confirming for this version).
  • Avoid orderBy on tree queries. Ordering by anything other than _lft during a read that then gets used for a move produces wrong bounds; apply presentation ordering after toTree().
  • Concurrency. Two simultaneous appends can collide on the same bounds. DB::transaction() with a pessimistic lock on the root, or a queue with a single worker, is the pragmatic fix.
  • Soft deletes + trees. Soft-deleted nodes still occupy _lft/_rgt, so descendants queries return them unless scoped. Decide explicitly whether your tree is "live only" or "includes trashed" and keep that consistent between display and repair logic.
  • Extension points. getLftName()/getRgtName()/getParentIdName()/getDepthName() let you match an existing schema; the moving/moved model events are the clean hook for cache invalidation of your tree's rendered output.
  • Testing. The package uses Testbench, and so should your own tree tests — build a small fixture tree in setUp(), then assert on _lft/_rgt after each mutation. Bound assertions catch corruption far earlier than a rendered UI will.
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
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons