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

Closure Table Laravel Package

jiaxincui/closure-table

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require jiaxincui/closure-table
    

    Publish the migration (if needed):

    php artisan vendor:publish --provider="Jiaxincui\ClosureTable\ClosureTableServiceProvider" --tag=migrations
    
  2. Define a Tree Model: Extend Jiaxincui\ClosureTable\Tree and implement getTreeTable():

    use Jiaxincui\ClosureTable\Tree;
    
    class Category extends Tree
    {
        protected $treeTable = 'categories'; // Your table name
        protected $parentKey = 'parent_id';  // Foreign key to parent
        protected $selfKey = 'id';           // Self-referencing key
    }
    
  3. Run Migrations:

    php artisan migrate
    
  4. First Use Case:

    // Create a root node
    $root = Category::create(['name' => 'Root']);
    
    // Create a child node
    $child = $root->children()->create(['name' => 'Child']);
    
    // Fetch the tree structure
    $tree = $root->withDescendants()->get();
    

Implementation Patterns

Common Workflows

  1. Hierarchy Management:

    // Add a child to a node
    $parent->children()->create(['name' => 'New Child']);
    
    // Move a node (reparent)
    $node->moveTo($newParent);
    
    // Reorder siblings
    $node->moveBefore($sibling);
    
  2. Querying:

    // Get all descendants (recursive)
    $descendants = $node->descendants;
    
    // Get ancestors (recursive)
    $ancestors = $node->ancestors;
    
    // Get siblings
    $siblings = $node->siblings;
    
    // Filter descendants by condition
    $activeChildren = $node->descendants()->where('is_active', true)->get();
    
  3. Tree Traversal:

    // Depth-first traversal (Laravel 8+)
    $node->eachDescendant(function ($descendant) {
        // Process each descendant
    });
    
    // Breadcrumbs
    $breadcrumbs = $node->getBreadcrumbs();
    
  4. Performance Optimization:

    // Eager-load descendants (avoid N+1)
    $node->loadDescendants();
    
    // Use `withDepth()` for depth-aware queries
    $tree = Category::withDepth()->get();
    

Integration Tips

  1. Custom Closure Table: Override getTreeTable() and getClosureTable() to use custom tables:

    protected $closureTable = 'category_closure';
    
  2. Soft Deletes: Extend Jiaxincui\ClosureTable\SoftDeletes for soft-deletable trees:

    use Jiaxincui\ClosureTable\SoftDeletes;
    
    class Category extends Tree
    {
        use SoftDeletes;
    }
    
  3. Scopes: Add custom scopes for filtering:

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }
    
  4. API Responses: Serialize trees with toTreeArray():

    return response()->json($node->toTreeArray());
    

Gotchas and Tips

Pitfalls

  1. Closure Table Sync:

    • Issue: Manual parent updates may break the closure table.
    • Fix: Always use moveTo(), moveBefore(), or moveAfter() instead of direct DB updates.
    • Debug: Run php artisan db:seed to reset test data if closures are corrupted.
  2. Performance:

    • Issue: Deep trees (>100 levels) may cause timeouts.
    • Fix: Use withDepth() and limit recursion in queries:
      $shallowTree = Category::withDepth()->whereDepth('<', 5)->get();
      
  3. Circular References:

    • Issue: Accidentally creating loops (e.g., A → B → A).
    • Fix: Validate before moving nodes:
      if ($node->isAncestorOf($newParent)) {
          throw new \Exception("Cannot create circular reference!");
      }
      
  4. Migration Conflicts:

    • Issue: Custom closure_table migrations may clash.
    • Fix: Publish and modify the default migration:
      php artisan vendor:publish --tag=migrations
      

Debugging Tips

  1. Inspect Closure Table:

    SELECT * FROM categories_closure WHERE ancestor = 1;
    

    (Replace 1 with your node ID.)

  2. Log Tree Structure:

    $node->eachDescendant(function ($descendant) {
        \Log::debug("Node {$descendant->id}: {$descendant->name}");
    });
    
  3. Check for Orphaned Entries:

    // Find nodes with no closure entries
    $orphans = Category::doesntHave('closureEntries')->get();
    

Extension Points

  1. Custom Closure Logic: Override syncClosureTable() to add business logic:

    protected function syncClosureTable()
    {
        // Custom logic before sync
        parent::syncClosureTable();
        // Custom logic after sync
    }
    
  2. Event Hooks: Listen for tree events:

    Category::moved(function ($node, $oldParent, $newParent) {
        // Trigger analytics, notifications, etc.
    });
    
  3. Custom Tree Methods: Add helper methods to your model:

    public function getPath()
    {
        return $this->ancestors->pluck('name')->implode(' > ') . ' > ' . $this->name;
    }
    
  4. Multi-Tenant Trees: Use model events to scope trees per tenant:

    protected static function boot()
    {
        static::addGlobalScope('tenant', function (Builder $builder) {
            $builder->where('tenant_id', auth()->id());
        });
    }
    
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.
cocosmos/filament-sticky-save-bar
patrickbussmann/oauth2-apple
3brs/enterprise-security-bundle
anousss007/vigilance
supportpal/eloquent-model
ardenexal/fhir-models
laravel-at/laravel-image-sanitize
romalytar/yammi-audit-log-laravel
ardenexal/fhir-validation
arshaviras/weather-widget
laravel-chronicle/core
sunchayn/nimbus
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope