kirschbaum-development/eloquent-power-joins
Eloquent Power Joins brings Laravel-style joins to Eloquent. Join via relationship definitions, reuse model scopes in join contexts, query relationship existence with joins, and sort by related columns/aggregations—all with cleaner, more readable queries.
Installation: Add the package via Composer:
composer require kirschbaum-development/eloquent-power-joins
For Laravel < 10, use 3.* version.
First Use Case: Replace manual joins with relationship-based joins.
// Before
User::select('users.*')->join('posts', 'posts.user_id', '=', 'users.id');
// After
User::joinRelationship('posts');
Key Entry Points:
joinRelationship(): Join tables using Eloquent relationships.powerJoinHas()/powerJoinWhereHas(): Replace whereHas/has with joins.orderByPowerJoins*(): Sort by related table columns/aggregations.Relationship-Based Joins
'posts.comments').User::joinRelationship('posts.comments')
->leftJoinRelationship('drafts');
Conditional Joins
User::joinRelationship('posts', fn($join) => $join->where('posts.published', true));
User::joinRelationship('posts.comments', [
'posts' => fn($join) => $join->where('posts.approved', true),
'comments' => fn($join) => $join->where('comments.spam', false),
]);
Model Scopes in Joins
published()) inside join callbacks:User::joinRelationship('posts', fn($join) => $join->published());
$query in scopes for joins.Existence Queries
whereHas with join-based alternatives:User::powerJoinWhereHas('posts', fn($join) => $join->where('posts.views', '>', 100));
User::powerJoinWhereHas('commentsThroughPosts', [
'comments' => fn($query) => $query->where('body', 'like', '%test%'),
]);
Sorting by Related Data
User::orderByPowerJoins('profile.city')
->orderByPowerJoinsAvg('posts.rating', 'desc');
Post::orderByLeftPowerJoinsCount('comments.votes');
joinRelationshipUsingAlias() for duplicate table joins:
Post::joinRelationshipUsingAlias('category.parent', 'category_alias');
UserProfile::joinRelationship('users', fn($join) => $join->withTrashed());
withGlobalScopes() (avoid type-hinting $builder in global scopes):
UserProfile::joinRelationship('users', fn($join) => $join->withGlobalScopes());
Scope Type-Hinting
$query in model scopes breaks join callbacks.Builder or no type-hint:
// ❌ Fails
public function scopePublished($query: Builder) { ... }
// ✅ Works
public function scopePublished($query) { ... }
Polymorphic Joins
Image::joinRelationship('imageable')) requires specifying the morphable type:
Image::joinRelationship('imageable', morphable: Post::class);
BelongsToMany Joins
User::joinRelationship('groups', [
'groups' => [
'groups' => fn($join) => $join->where('groups.active', true),
'group_members' => fn($join) => $join->where('group_members.role', 'admin'),
],
]);
Aggregation Sorting
orderByPowerJoins* methods may return unexpected results if the joined table has no matching rows (e.g., NULL for COUNT).Post::orderByLeftPowerJoinsCount('comments.votes');
Global Scopes in Joins
$builder type-hinting fail silently.toSql() to verify generated queries:
User::joinRelationship('posts')->toSql();
Post::joinRelationshipUsingAlias('category', 'category_alias');
powerJoinHas vs. native whereHas—joins may be slower for complex subqueries.Custom Join Logic
config/eloquent-power-joins.php) to override default behaviors (e.g., soft-delete handling).Macros
use KirschbaumDevelopment\PowerJoins\PowerJoins;
QueryBuilder::macro('customJoin', function($relationship) {
return $this->joinRelationship($relationship)->where(...);
});
Testing
$mock = Mockery::mock();
$mock->shouldReceive('where')->once();
User::joinRelationship('posts', $mock);
Laravel 11+ Compatibility
```markdown
### Example Debugging Workflow
1. **Problem**: `powerJoinWhereHas` returns incorrect results.
2. **Steps**:
- Compare `toSql()` output with native `whereHas`.
- Check for missing `ON` clauses in nested joins.
- Verify table aliases in complex relationships.
3. **Fix**: Adjust callbacks or use left joins for nullable fields.
How can I help you explore Laravel packages today?