Weave Code
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 Adjacency List Laravel Package

staudenmeir/laravel-adjacency-list

Laravel Eloquent extension for recursive tree and graph relationships using SQL common table expressions. Traverse ancestors, descendants, and paths in adjacency-list data across MySQL, Postgres, SQLite, SQL Server, and more; supports one-to-many trees and many-to-many graphs.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require staudenmeir/laravel-adjacency-list
    

    Ensure your database is compatible (MySQL 8.0+, PostgreSQL 9.4+, etc.).

  2. Model Integration: Add the trait to your model:

    use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
    
    class Category extends Model
    {
        use HasRecursiveRelationships;
    }
    
  3. First Query: Fetch a tree structure with roots:

    $categories = Category::tree()->get();
    

    Or load descendants of a specific node:

    $category = Category::find(1);
    $descendants = $category->descendants;
    

Implementation Patterns

Common Workflows

  1. Hierarchical Data Fetching:

    // Fetch a nested category tree with depth-limited children
    $tree = Category::tree()->withMaxDepth(2)->get()->toTree();
    
  2. Eager Loading with Constraints:

    // Load descendants with eager-loaded relationships
    $categories = Category::with(['descendants.posts'])->get();
    
  3. Path-Based Filtering:

    // Find nodes with a specific path (e.g., "1.2.3")
    $node = Category::where('path', 'like', '1.2.%')->first();
    
  4. Cycle-Safe Queries:

    // Enable cycle detection for recursive queries
    Category::enableCycleDetection();
    $safeDescendants = Category::find(1)->descendants;
    
  5. Custom Path Columns:

    // Add slug-based paths to results
    class Category extends Model
    {
        public function getCustomPaths()
        {
            return [
                [
                    'name' => 'slug_path',
                    'column' => 'slug',
                    'separator' => '/',
                ],
            ];
        }
    }
    

Integration Tips

  • API Responses: Use toTree() to return nested JSON structures:
    return response()->json($categories->toTree());
    
  • Admin Panels: Combine with breadthFirst()/depthFirst() for intuitive tree visualization.
  • Caching: Cache tree structures with withMaxDepth() to avoid recomputing large hierarchies:
    Cache::remember('category-tree', now()->addHours(1), function () {
        return Category::tree()->withMaxDepth(3)->get()->toTree();
    });
    

Gotchas and Tips

Pitfalls

  1. Database Compatibility:

    • SQLite < 3.8.3 or MySQL < 8.0 will throw errors due to missing CTE support.
    • Fix: Upgrade your database or use a fallback (e.g., materialized paths).
  2. Cycle Detection Overhead:

    • Enabling enableCycleDetection() adds query complexity. Only enable it if cycles are possible.
    • Tip: Use includeCycleStart() sparingly—it impacts performance.
  3. Depth Constraints:

    • whereDepth() filters after building the full subtree. Use withMaxDepth() for large trees:
      // Bad (inefficient for deep trees)
      $deepNodes = Category::find(1)->descendants()->whereDepth('>', 5)->get();
      
      // Good (limits query scope)
      $deepNodes = Category::withMaxDepth(10, function () {
          return Category::find(1)->descendants;
      })->whereDepth('>', 5)->get();
      
  4. N+1 Queries:

    • toTree() creates nested arrays but doesn’t eager-load relationships. Use loadTreeRelationships():
      $tree = Category::tree()->get()->loadTreeRelationships()->toTree();
      
  5. Custom Path Conflicts:

    • Overriding getPathName() or getDepthName() may clash with existing columns. Use unique names:
      public function getDepthName() { return 'tree_depth'; }
      

Debugging Tips

  • Query Logging: Enable Laravel’s query logging to inspect CTEs:
    DB::enableQueryLog();
    $tree = Category::tree()->get();
    dd(DB::getQueryLog());
    
  • Cycle Detection: Check for is_cycle in results when debugging infinite loops:
    $descendants = Category::find(1)->descendants;
    $cycles = $descendants->filter(fn ($node) => $node->is_cycle);
    
  • Performance: Use explain() to analyze query plans for slow recursive queries:
    DB::connection()->enableQueryLog();
    Category::find(1)->descendants()->get();
    tap(DB::getQueryLog(), function ($logs) {
        dd(DB::connection()->getPdo()->query('EXPLAIN ' . $logs[0]['query'])->fetchAll());
    });
    

Extension Points

  1. Custom Scopes: Extend the trait to add domain-specific scopes:
    class Category extends Model
    {
        public function scopeActiveTree($query)
        {
            return $query->where('active', true)->tree();
        }
    }
    
  2. Query Modifiers: Override getInitialQuery() or getRecursiveQuery() to inject constraints:
    public function getInitialQuery($query)
    {
        return $query->where('published', true);
    }
    
  3. Hybrid Relationships: Combine with Laravel’s built-in relationships for mixed hierarchies:
    public function tags()
    {
        return $this->belongsToMany(Tag::class)->withPivot('order');
    }
    
    public function recursiveTags()
    {
        return $this->hasManyOfDescendantsAndSelf(Tag::class, 'tags');
    }
    
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai