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

Laravel Recursive Relationships Laravel Package

paxha/laravel-recursive-relationships

Laravel package for defining and working with recursive relationships in Eloquent models—query parent-child hierarchies, trees, and nested structures with ease. (Approx 160 chars - need closer to 300) Let me expand to ~300 chars: Laravel package for defining and querying recursive relationships in E...

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and check compatibility first. The package's last release was December 2020 (Laravel 8 era) and it has zero dependents, so before writing a line of code confirm it installs and boots on your Laravel/PHP version:

composer require paxha/laravel-recursive-relationships

If composer resolution fails against Laravel 10/11, treat that as a signal to vendor the CTE query yourself rather than fighting the constraint.

2. Confirm your database supports recursive CTEs. The package is built on WITH RECURSIVE common table expressions. That means MySQL 8.0.1+, PostgreSQL, SQLite 3.8.3+, or SQL Server. MySQL 5.7 and MariaDB (older versions) will not work — this is the single most common reason the package appears "broken."

3. Give your table a self-referencing foreign key. The model needs a nullable parent_id (or your configured key) pointing back at the same table:

Schema::create('categories', function (Blueprint $table) {
    $table->id();
    $table->foreignId('parent_id')->nullable()->constrained('categories')->nullOnDelete();
    $table->string('name');
    $table->timestamps();

    $table->index('parent_id'); // do not skip this
});

4. Add the trait to the model. That is the whole integration surface:

use Paxha\RecursiveRelationships\Traits\HasRecursiveRelationships;

class Category extends Model
{
    use HasRecursiveRelationships;
}

Where to look first: the trait itself (vendor/paxha/laravel-recursive-relationships/src/Traits/HasRecursiveRelationships.php) — it's small, and reading the CTE it builds tells you exactly which method names and column assumptions you're working with. Do this before trusting the README, because the README lags the code on a package this old.

First use case: a nested category/comment/org-chart tree. Load a root and pull its whole subtree in one query instead of recursing in PHP:

$root = Category::find(1);

foreach ($root->descendants as $node) {
    // flat list of every node beneath $root, one DB round trip
}

Implementation Patterns

Render a tree from a flat recursive fetch. Fetch the subtree once, then group in memory rather than querying per level:

$nodes = Category::find($rootId)->descendants->push(Category::find($rootId));

$tree = $nodes->groupBy('parent_id');
// recurse over $tree in a Blade partial / view component

This is the pattern that actually pays off day-to-day: one CTE query instead of N+1 per depth level.

Breadcrumbs via ancestors. The mirror operation is the ancestor chain — perfect for breadcrumb trails and permission inheritance walks:

$trail = $category->ancestors->reverse(); // root → current

Scope everything to a subtree. For multi-tenant or per-branch admin screens, get a subtree's ID list once and reuse it as a whereIn guard:

$subtreeIds = $node->descendants->pluck('id')->push($node->id);

Post::whereIn('category_id', $subtreeIds)->get();

This is the cleanest way to combine recursive relationships with non-recursive queries without leaking cross-branch data.

Guard writes with a "is descendant of" check. Before reparenting a node, make sure you aren't creating a cycle or moving a node under itself:

$isDescendant = $newParent->ancestors->contains('id', $node->id);
abort_if($isDescendant, 422, 'Cannot move a node under its own descendant');

Eager-load where you can, but expect limits. Standard with() works for the direct parent/children, but the recursive relations are better fetched explicitly rather than mixed into a large eager-load graph — the generated CTE is per-query, so it doesn't compose cheaply with deep with() chains.

Testing pattern. Seed a 3-level fixture (root → child → grandchild) in setUp and assert both directions: descendants contains the grandchild, ancestors on the grandchild contains the root. Use SQLite in-memory only if it's 3.8.3+; otherwise point tests at MySQL 8.

Gotchas and Tips

  • Database engine is a hard dependency. If your prod DB is MySQL 5.7 or MariaDB, the recursive CTE will fail at runtime — not at install. Verify the engine in every environment (local, CI, prod) before committing to the package.
  • Cycles cause infinite recursion. Nothing in the package prevents A → B → A. Enforce the self/descendant check on write (see above) or add a DB-level guard. A cycle will hang the query or blow the CTE depth limit.
  • Missing parent_id index kills performance. Every recursive step joins on parent_id; without an index the CTE degrades badly on large tables. Add it explicitly — the package can't do that for you.
  • Depth limits are database defaults, not package settings. MySQL's cte_max_recursion_depth (default 1000) and Postgres' behavior will surface as cryptic errors on very deep trees. Set it deliberately if you support deep hierarchies.
  • Soft deletes are a trap. If your model uses SoftDeletes, verify how the CTE treats trashed intermediate nodes — a deleted parent can silently orphan its subtree from the recursive result. Test a trashed mid-level node explicitly.
  • Don't assume the README matches the code. With one release in 2020 and no dependents, the trait source is the source of truth for method names (descendants/ancestors/children/parent may be named or behave subtly differently than the docs claim). Read the trait, then write the test.
  • Cache computed subtree IDs. Recursive queries are cheap but not free. For read-heavy menus and permission trees, cache the flattened ID list or rendered tree and bust it on any write to the table.
  • Extension point: because the logic lives in a small trait, the practical escape hatch is to override the recursive method in your model (or a parent trait) with your own CTE builder — useful if you need where filters, tenant scoping, or union across multiple tables, which the stock package won't give you.
  • Budget for ownership. At 27 stars, zero dependents, and a stale release, you are effectively the maintainer. If the integration grows beyond a single menu tree, consider replacing it with a closure table or a hand-written WITH RECURSIVE scope you control — the migration cost is low and the longevity risk is real.
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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log