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

Tree Laravel Package

lex/tree

Lex Tree is a Laravel/PHP package for working with hierarchical tree structures, helping developers model, organize, and traverse nested data with a simple, reusable API for tree-based application features.

View on GitHub
Deep Wiki
Context7

Getting Started

This release's headline change is that TreeTrait can now be dropped into your own model to satisfy TreeInterface — previously you were likely locked into extending a package-provided base model. That makes it the first thing to check.

  1. Install the package (composer require), and run the migrations/publish step if the package ships them.
  2. Open TreeInterface first — it is the contract your application code will type-hint against (TreeInterface $node), so it defines the real public API: parent/children accessors, ancestors/descendants, roots, leaf checks, move/append operations.
  3. Open TreeTrait second — it is the default implementation of that contract and tells you exactly which columns and relations it expects on your table.
  4. First use case: take an existing Eloquent model (e.g. Category) that already has a parent_id column, add use TreeTrait; and implements TreeInterface, then call $category->children / $category->descendants().

Minimal shape:

class Category extends Model implements TreeInterface
{
    use TreeTrait;

    protected $fillable = ['name', 'parent_id'];
}

Before writing anything custom, confirm whether the package's base model offers extra behaviour (global scopes, ordering, observers) that the trait alone does not — the trait is the portable subset, not necessarily a superset.

Implementation Patterns

Trait on an existing model (the new v1.1 path). Prefer this when you already have a table with domain columns and don't want a package-owned base class in your inheritance chain. Keep your existing extends Model and let the trait supply tree behaviour. This is the pattern to reach for in brownfield apps.

Interface-typed boundaries. Because behaviour is now contract-driven, type-hint TreeInterface in services, actions, and Blade components rather than a concrete class. That lets you have several tree models (categories, pages, org units) share one renderer/traversal service.

Multiple tree models. Define a small abstract base in your app that applies the trait + interface, then extend it per entity — you get the portability of the trait without repeating the boilerplate in five models.

Traversal in services, not views. Wrap descendants(), ancestors(), and roots() in a query/service object so you can swap recursive-CTE implementations later and so controllers stay thin. Return collections and let API resources flatten them.

Rendering a tree. For admin dropdowns, fetch an ordered flat list once and build the indented labels in PHP — this avoids a query per level. For nested JSON, build the structure from a single flat fetch keyed by parent_id rather than lazy-loading children recursively.

Move/append operations. Route all re-parenting through the trait's move/append method (or a wrapper action) instead of assigning parent_id directly, so any path/depth bookkeeping the package performs stays consistent. Wrap moves in a transaction when several nodes shift.

Integration points. Pair with:

  • route-model binding by slug/path if the package exposes path helpers,
  • authorization policies that check "can move node into target" (cycle + permission),
  • caching of the tree in a repository with explicit invalidation on create/move/delete.

Gotchas and Tips

  • Trait without interface. use TreeTrait; alone compiles fine but breaks any TreeInterface type-hint or container binding. Always add implements TreeInterface. Conversely, implementing the interface without the trait means writing every method yourself — a silent, large surface area.
  • Method collisions. Your model may already define children(), parent(), or ancestors(). PHP resolves model methods over trait methods, so your version silently wins and the package's expectations break. Audit your existing models for colliding names before applying the trait; use insteadof/aliasing only if you genuinely need both.
  • Column expectations. The trait assumes specific column names (parent_id and possibly path, depth, order/sort). Read the trait's property references and migration stubs before assuming defaults. Mismatched column names fail at query time, not at boot.
  • $fillable / $guarded. Tree bookkeeping columns are usually written internally; keeping them out of $fillable prevents mass-assignment from silently corrupting paths when you update($request->all()).
  • Sibling ordering is not guaranteed. If the trait doesn't impose an ORDER BY, descendants come back in whatever order the DB returns. Add an explicit sort column and order scope; don't rely on id ordering.
  • Cycles. Direct parent_id assignment lets you create A -> B -> A, which will hang recursive traversal or produce infinite loops. Guard in a move action by checking the target is not a descendant of the node.
  • N+1 and recursion depth. Lazy-loading children recursively is the classic performance trap. Fetch a flat, ordered result set and assemble in memory, or push traversal into a recursive CTE where the driver supports it.
  • Driver differences. Recursive CTE syntax and support vary across MySQL 8+, MariaDB, SQLite, and Postgres; older MySQL versions can't do it at all. Verify against your production engine, not just the test SQLite DB.
  • Delete semantics. Decide and test explicitly: does deleting a parent orphan children (parent_id nulled), cascade, or block? The trait may rely on a FK constraint you never added.
  • Debugging. Log the actual SQL for traversal methods (DB::listen or ->dd()); most "wrong tree" bugs are stale path/depth columns from a direct parent_id write, not a broken trait.
  • Extension points. If the trait hardcodes column names, either match them or override the relevant methods in your model — but note that overrides are exactly where a future package release can diverge from your expectations, so keep the override list short and covered by tests.
  • Test with a fixture tree. Seed a 3-level tree in a factory and assert ancestors/descendants/roots counts; these tests catch a surprising share of upgrade breakage from trait changes.

The overall signal from v1.1.0 is that the package is moving toward contract-based, composable usage rather than inheritance — lean into that by typing against TreeInterface everywhere and treating TreeTrait as a replaceable implementation detail.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core