audunru/eager-load-pivot-relations
Install the package:
composer require audunru/eager-load-pivot-relations
Apply the trait to the model that defines the BelongsToMany relationship (e.g., Plan or Item in the example):
use audunru\EagerLoadPivotRelations\EagerLoadPivotTrait;
class Plan extends Model
{
use EagerLoadPivotTrait;
public function items()
{
return $this->belongsToMany('Item', 'plan_item')
->using('PlanItem')
->withPivot('unit_id', 'qty', 'price');
}
}
Define pivot relations in your pivot model:
class PlanItem extends Pivot
{
public function unit()
{
return $this->belongsTo('Unit');
}
}
Eager-load pivot relations using the pivot keyword (or a custom alias):
$plans = Plan::with('items.pivot.unit')->get();
Query a Plan with its Items and their associated Unit (via pivot):
$plan = Plan::with('items.pivot.unit')->find(1);
$unitName = $plan->items->first()->pivot->unit->name; // Access nested relation
Define the BelongsToMany relationship with ->using() and ->withPivot():
public function items()
{
return $this->belongsToMany('Item')
->using('PlanItem')
->withPivot(['unit_id', 'qty']);
}
Eager-load pivot relations in a single query:
// Basic pivot relation
Plan::with('items.pivot.unit')->get();
// Nested pivot relations
Plan::with([
'items.pivot.unit.category',
'items.pivot.unit.someBelongsToManyRelation.pivot.anotherRelation'
])->get();
Access loaded data:
foreach ($plan->items as $item) {
$unit = $item->pivot->unit; // or $item->planItem->unit if using custom alias
$qty = $item->pivot->qty;
}
Rename pivot to a domain-specific alias (e.g., planItem):
// In the BelongsToMany definition
public function items()
{
return $this->belongsToMany('Item')
->using('PlanItem')
->withPivot(['unit_id', 'qty'])
->as('planItem'); // Custom alias
}
// Apply the trait to the model defining the relation (Plan)
class Plan extends Model
{
use EagerLoadPivotTrait;
}
// Query with the custom alias
Plan::with('items.planItem.unit')->get();
Use conditional eager-loading based on request parameters:
public function show(Plan $plan)
{
$eagerLoads = ['items.pivot.unit'];
if (request('include_qty')) {
$eagerLoads[] = 'items.pivot.qty';
}
return Plan::with($eagerLoads)->findOrFail($plan->id);
}
Centralize pivot relation loading in a repository:
class PlanRepository
{
public function withPivotRelations(Plan $plan, array $relations = [])
{
$defaultRelations = ['items.pivot.unit'];
$relations = array_merge($defaultRelations, $relations);
return Plan::with($relations)->findOrFail($plan->id);
}
}
Leverage pivot relations in API responses:
public function toArray($request)
{
return [
'id' => $this->id,
'items' => $this->items->map(function ($item) {
return [
'name' => $item->name,
'unit' => $item->pivot->unit->name ?? null,
'quantity' => $item->pivot->qty,
];
}),
];
}
Add a scope to filter based on pivot relations:
class PlanScope
{
public function withUnits($query, array $unitIds)
{
return $query->with(['items.pivot.unit' => function ($query) use ($unitIds) {
$query->whereIn('units.id', $unitIds);
}]);
}
}
// Usage:
Plan::withUnits([1, 2, 3])->get();
Trait Placement:
Item instead of Plan).BelongsToMany relationship (the "parent" model).Plan::items(), apply the trait to Plan, not Item.Missing ->using():
->using('PivotModel') in the BelongsToMany definition.$this->belongsToMany('Item')->using('PlanItem');
Custom Alias Mismatch:
planItem) in queries but not defining ->as('planItem') in the relationship.N+1 Queries Persist:
with() clauses for BelongsToMany relationships.Laravel Version Mismatch:
Verify Eager-Loading:
Use Laravel Debugbar or toSql() to confirm queries:
$query = Plan::with('items.pivot.unit')->toSql();
dd($query); // Check generated SQL
Check Pivot Model:
Ensure the pivot model extends Pivot and has the correct table:
class PlanItem extends Pivot
{
protected $table = 'plan_item'; // Explicitly set if not default
}
Inspect Relationships: Dump the relationship definition to debug:
dd($plan->items->getRelation()); // Check if pivot relations are loaded
Fallback to Raw SQL: If eager-loading fails, use raw queries as a temporary workaround:
Plan::with(['items' => function ($query) {
$query->select(['items.*'])
->join('plan_item', 'items.id', '=', 'plan_item.item_id')
->join('units', 'plan_item.unit_id', '=', 'units.id')
->selectRaw('items.*, units.name as unit_name');
}])->get();
Selective Eager-Loading: Only load pivot relations you need:
// Bad: Loads all pivot attributes/relations
Plan::with('items.*')->get();
// Good: Explicitly load only what's needed
Plan::with(['items.pivot.unit', 'items.pivot.qty'])->get();
Limit Nested Relations:
Avoid deeply nested pivot relations (e.g., pivot.unit.category.subcategory) unless necessary, as they increase query complexity.
Cache Frequently Accessed Data: Cache results of pivot-heavy queries:
$plans = Cache::remember("plans_with_units_{$planId}", now()->addHours(1), function () use ($planId) {
return Plan::with('items.pivot.unit')->find($planId);
});
Use withCount for Metrics:
Combine eager-loading with withCount to avoid additional queries:
Plan::with(['items.pivot.unit', 'items.pivot.qty'])
->withCount('items as item_count')
->get();
getEagerLoads() method to modify eager-loading behavior:
class Plan extends Model
{
use EagerLoadPivot
How can I help you explore Laravel packages today?