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

Eloquent Has Many Deep Laravel Package

staudenmeir/eloquent-has-many-deep

Laravel Eloquent extension for “deep” has-many-through relationships across unlimited intermediate models. Supports many-to-many and polymorphic paths, combinations, and some third-party packages. Define relations by concatenating existing ones or configuring keys manually.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require staudenmeir/eloquent-has-many-deep:"^1.7"
    

    Ensure compatibility with your Laravel version (check version table).

  2. Basic Usage: Add the trait to your model:

    use Staudenmeir\EloquentHasManyDeep\HasRelationships;
    

    Define a deep relationship (e.g., Country → User → Post → Comment):

    public function comments()
    {
        return $this->hasManyDeep(Comment::class, [User::class, Post::class]);
    }
    
  3. First Use Case: Fetch all comments for a country:

    $country = Country::find(1);
    $comments = $country->comments()->get();
    

Where to Look First

  • README.md: Focus on the Usage section for examples.
  • Concatenating Relationships: Start here if you already have existing relationships to chain.
  • Manual Definition: Use this if you need to define the path from scratch.

Implementation Patterns

Workflows

  1. Chaining Existing Relationships:

    // Country → User → Post → Comment
    public function comments()
    {
        return $this->hasManyDeepFromRelations(
            $this->users(), // Country → User
            fn() => (new Post())->comments() // User → Comment
        );
    }
    
    • Pros: Reuses existing relationships, cleaner code.
    • Cons: Constraints (e.g., where) are not automatically applied unless using hasManyDeepFromRelationsWithConstraints.
  2. Manual Path Definition:

    public function permissions()
    {
        return $this->hasManyDeep(
            Permission::class,
            ['role_user', Role::class] // Pivot + intermediate model
        );
    }
    
    • Pros: Full control over keys and constraints.
    • Cons: More verbose, requires explicit key definitions.
  3. Polymorphic Relationships:

    public function postComments()
    {
        return $this->hasManyDeep(
            Comment::class,
            [Post::class],
            [null, ['commentable_type', 'commentable_id']]
        );
    }
    
    • Use arrays for polymorphic keys (e.g., ['type', 'id']).
  4. Many-to-Many Paths:

    public function permissions()
    {
        return $this->hasManyDeep(
            Permission::class,
            ['role_user', Role::class, 'permission_role']
        );
    }
    
    • Include pivot tables in the intermediate array.

Integration Tips

  • Constraints: Apply constraints after defining the relationship:

    $this->comments()->where('comments.approved', true);
    

    For complex constraints, use hasManyDeepFromRelationsWithConstraints with callables.

  • Eager Loading: Use with() to avoid N+1 queries:

    Country::with('comments')->find(1);
    
  • Soft Deletes: Enable soft deletes for intermediate models:

    $this->comments()->withTrashed();
    
  • Unique Results: Use distinct() or unique() for duplicate-free results:

    $this->comments()->distinct();
    
  • Reversing Relationships: Define inverse relationships manually (e.g., Comment → Post → User → Country).


Gotchas and Tips

Pitfalls

  1. Key Mismatches:

    • Issue: Forgetting to swap foreign/local keys in pivot tables (e.g., ManyToMany).
      // Wrong: Keys not swapped for pivot table.
      $this->hasManyDeep(Permission::class, ['role_user', Role::class], ['user_id', 'id']);
      
    • Fix: Swap keys for the "right" side of the pivot:
      $this->hasManyDeep(
          Permission::class,
          ['role_user', Role::class],
          ['user_id', 'role_id'], // Swapped for pivot
          ['id', 'id']
      );
      
  2. Constraint Scope:

    • Issue: Constraints on intermediate relationships (e.g., where('posts.published', true)) may not work if table aliases are missing.
    • Fix: Qualify column names with table aliases:
      $this->comments()->where('posts.published', true);
      
  3. Polymorphic Ambiguity:

    • Issue: Polymorphic relationships may return unexpected results if the type column is not correctly specified.
    • Fix: Always use arrays for polymorphic keys:
      [null, ['commentable_type', 'commentable_id']]
      
  4. Soft Deletes:

    • Issue: Intermediate models with soft deletes may not return expected results if withTrashed() is omitted.
    • Fix: Explicitly include soft-deleted records:
      $this->comments()->withTrashed();
      
  5. Composite Keys:

    • Issue: Forgetting to use Staudenmeir\EloquentHasManyDeep\CompositeKey for multi-column relationships.
    • Fix: Define composite keys explicitly:
      use Staudenmeir\EloquentHasManyDeep\CompositeKey;
      $this->hasManyDeep(..., [new CompositeKey(['col1', 'col2'])]);
      

Debugging Tips

  • Query Logs: Enable query logging to inspect generated SQL:

    DB::enableQueryLog();
    $comments = $this->comments()->get();
    dd(DB::getQueryLog());
    
  • Relationship Debugging: Use toSql() to preview the query:

    $query = $this->comments()->toSql();
    dd($query);
    
  • IDE Helper: Install the IDE Helper for autocompletion:

    composer require --dev staudenmeir/eloquent-has-many-deep-ide-helper
    

Extension Points

  1. Custom Constraints: Extend the relationship class to add custom constraints:

    class CustomHasManyDeep extends \Staudenmeir\EloquentHasManyDeep\HasManyDeep
    {
        public function customConstraint()
        {
            return $this->where('comments.spam', false);
        }
    }
    
  2. Third-Party Integration:

    • Example: Integrate with laravel-adjacency-list for tree/graph relationships:
      $this->hasManyDeepFromRelations(
          $this->children(), // Tree relationship
          fn() => (new Post())->comments()
      );
      
  3. Dynamic Relationships: Use closures to define dynamic paths:

    public function dynamicComments($level)
    {
        $path = [$this->users()];
        for ($i = 0; $i < $level; $i++) {
            $path[] = fn() => (new Post())->comments();
        }
        return $this->hasManyDeepFromRelations(...$path);
    }
    
  4. Performance Optimization:

    • Batch Loading: Use load() for selective loading:
      $country = Country::find(1);
      $country->load('comments');
      
    • Cursor Pagination: For large datasets:
      $this->comments()->cursor()->paginate(20);
      
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.
hamzi/corewatch
minionfactory/raw-hydrator
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