Installation:
composer require sleeping-owl/with-join
Add the service provider to config/app.php under providers:
SleepingOwl\WithJoin\WithJoinServiceProvider::class,
First Use Case:
Replace a with() eager load with a references() call to convert a belongsTo subquery into a LEFT JOIN:
// Before (N+1 queries)
Post::with('author')->where('title', 'like', '%test%')->get();
// After (Single query with JOIN)
Post::with('author')->references('author')->where('title', 'like', '%test%')->get();
Usage section for syntax and examples.vendor/sleeping-owl/with-join/src/WithJoinServiceProvider.php for provider logic.vendor/sleeping-owl/with-join/src/WithJoinMacros.php for core functionality.Eager Loading + JOIN Conversion:
// Traditional eager load (N+1)
User::with('posts')->get();
// Optimized JOIN (1 query)
User::with('posts')->references('posts')->get();
Conditional JOINs:
Use includes() for model-level defaults or dynamic relations:
// Model definition
class User extends Model {
protected $includes = ['posts', 'profile'];
}
// Query (uses JOINs for included relations)
User::where('name', 'John')->get();
Hybrid Queries:
Mix with() and references() for selective optimization:
// JOIN 'author' but eager-load 'comments'
Post::with(['comments'])->references('author')->get();
Query Scopes: Encapsulate optimized queries in scopes:
class PostScope {
public function scopeWithAuthor($query) {
return $query->with('author')->references('author');
}
}
Dynamic Relations:
Use references() with dynamic relations (Laravel 5.3+):
$relation = 'author';
Post::with($relation)->references($relation)->get();
Ordering by Related Fields: Leverage JOINs for efficient sorting:
Post::with('author')->references('author')->orderBy('author.name')->get();
Conditional JOINs:
Combine with when() for context-aware queries:
Post::when($showAuthor, function ($query) {
return $query->with('author')->references('author');
})->get();
Unsupported Relations:
belongsTo, belongsToMany, and hasOne relations. Avoid hasMany or morphTo without testing.LEFT JOIN clauses for unsupported relations.Column Ambiguity:
JOINs may cause SQL errors if column names conflict (e.g., created_at in both tables).
DB::raw:
Post::with('author')->references('author')->select('posts.*', 'authors.name as author_name')->get();
Model Caching:
The package doesn’t cache relation metadata. Repeated references() calls may re-process the same relations.
Laravel Version Mismatch: The package was last updated in 2014 and may not support Laravel 8/9 features (e.g., query builder changes).
Over-Eager Loading:
Applying references() to all relations can bloat queries. Use selectively.
DB::enableQueryLog() to identify bottlenecks.Query Logs: Enable query logging to verify JOINs:
DB::enableQueryLog();
Post::with('author')->references('author')->get();
dd(DB::getQueryLog());
Common Errors:
$with or $includes.Performance Gain:
Measure impact with Benchmark:
$time = microtime(true);
Post::with('author')->get(); // N+1
$nPlusOneTime = microtime(true) - $time;
$time = microtime(true);
Post::with('author')->references('author')->get(); // JOIN
$joinTime = microtime(true) - $time;
// Compare $nPlusOneTime vs $joinTime
Combining with Other Packages:
Works seamlessly with laravel-scout, spatie/laravel-activitylog, etc., as it operates at the query level.
Customizing JOIN Clauses:
Extend the package by overriding the buildJoin method in a macro:
QueryBuilder::macro('customJoin', function ($relation) {
// Custom logic here
return $this->leftJoin(...);
});
Testing: Mock the package’s macros in tests:
QueryBuilder::shouldReceive('references')->andReturnSelf();
Fallback Behavior:
If references() fails (e.g., unsupported relation), the query falls back to eager loading. No exceptions are thrown.
How can I help you explore Laravel packages today?