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.
Installation:
composer require staudenmeir/eloquent-has-many-deep:"^1.7"
Ensure compatibility with your Laravel version (check versions table).
Basic Usage: Add the trait to your model:
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
First Use Case:
Define a deep relationship (e.g., Country → User → Post → Comment):
public function comments()
{
return $this->hasManyDeep(Comment::class, [User::class, Post::class]);
}
Fetch results:
$country = Country::find(1);
$comments = $country->comments()->get();
Concatenating Existing Relationships:
Use hasManyDeepFromRelations() for chaining existing relationships:
public function comments()
{
return $this->hasManyDeepFromRelations(
$this->users(), // Country → User
(new User())->posts(), // User → Post
(new Post())->comments() // Post → Comment
);
}
Manual Definition: Specify intermediate models, foreign keys, and local keys:
public function permissions()
{
return $this->hasManyDeep(
Permission::class,
['role_user', Role::class],
['user_id', 'role_id', 'permission_id'],
['id', 'role_id', 'id']
);
}
Constraints and Scopes: Apply constraints to intermediate steps:
public function activeComments()
{
return $this->hasManyDeep(Comment::class, [User::class, Post::class])
->where('posts.published', true)
->where('comments.approved', true);
}
Polymorphic Relationships:
Handle polymorphic keys (e.g., commentable_type, commentable_id):
public function postComments()
{
return $this->hasManyDeep(
Comment::class,
[Post::class],
[null, ['commentable_type', 'commentable_id']]
);
}
Many-to-Many Paths: Include pivot tables in the chain:
public function permissions()
{
return $this->hasManyDeep(
Permission::class,
['role_user', Role::class, 'permission_role']
);
}
withTrashed() or withoutTrashed() on intermediate relationships.with() for performance:
$countries = Country::with('comments')->get();
laravel-adjacency-list or compoships for tree/graph structures.Staudenmeir\EloquentHasManyDeep\CompositeKey for multi-column relationships.Key Mismatches:
null for defaults:
$this->hasManyDeep(Comment::class, [User::class, Post::class], [null, 'custom_user_id']);
['role_user', Role::class], // Keys: ['user_id', 'role_id', 'role_id']
Constraint Ambiguity:
->where('posts.published', true) // Not ->where('published', true)
Performance:
select() to limit columns:
->select('comments.id', 'comments.body')
hasManyDeep in loops; eager-load instead.Soft Deletes:
withTrashed():
->withTrashed() // Applies to all intermediate steps
IDE Helpers:
composer require --dev staudenmeir/eloquent-has-many-deep-ide-helper
->toSql() and ->getBindings() to inspect generated queries:
$query = $this->comments()->toSql();
$bindings = $this->comments()->getBindings();
Custom Constraints: Extend the query builder with custom constraints:
public function scopeActive($query)
{
return $query->where('active', true);
}
Then use:
$this->comments()->active();
Dynamic Relationships: Use closures for dynamic paths:
public function dynamicComments($userModel)
{
return $this->hasManyDeep(Comment::class, [$userModel, Post::class]);
}
Reverse Relationships:
For inverse paths (e.g., Comment → Post → User → Country), manually define keys:
public function country()
{
return $this->hasManyDeep(
Country::class,
[User::class, Post::class],
['user_id', 'country_id'],
['id', 'user_id']
);
}
Unique Results:
Use distinct() or groupBy to avoid duplicates:
->distinct()
// or
->groupBy('comments.id')
How can I help you explore Laravel packages today?