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

Eager Load Pivot Relations Laravel Package

audunru/eager-load-pivot-relations

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require audunru/eager-load-pivot-relations
    
  2. 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');
        }
    }
    
  3. Define pivot relations in your pivot model:

    class PlanItem extends Pivot
    {
        public function unit()
        {
            return $this->belongsTo('Unit');
        }
    }
    
  4. Eager-load pivot relations using the pivot keyword (or a custom alias):

    $plans = Plan::with('items.pivot.unit')->get();
    

First Use Case: Fetching Pivot Relations

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

Implementation Patterns

Core Workflow: Eager-Loading Pivot Relations

  1. Define the BelongsToMany relationship with ->using() and ->withPivot():

    public function items()
    {
        return $this->belongsToMany('Item')
            ->using('PlanItem')
            ->withPivot(['unit_id', 'qty']);
    }
    
  2. 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();
    
  3. Access loaded data:

    foreach ($plan->items as $item) {
        $unit = $item->pivot->unit; // or $item->planItem->unit if using custom alias
        $qty = $item->pivot->qty;
    }
    

Advanced Patterns

1. Custom Pivot Accessor Aliases

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();

2. Dynamic Eager-Loading in Controllers

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);
}

3. Scoped Eager-Loading in Repositories

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);
    }
}

4. API Resource Transformation

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,
            ];
        }),
    ];
}

5. Query Scoping for Pivot Relations

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();

Gotchas and Tips

Pitfalls

  1. Trait Placement:

    • Mistake: Applying the trait to the wrong model (e.g., Item instead of Plan).
    • Fix: The trait must be on the model that defines the BelongsToMany relationship (the "parent" model).
    • Example: For Plan::items(), apply the trait to Plan, not Item.
  2. Missing ->using():

    • Mistake: Forgetting to specify ->using('PivotModel') in the BelongsToMany definition.
    • Fix: Always use a custom pivot model for relations to work:
      $this->belongsToMany('Item')->using('PlanItem');
      
  3. Custom Alias Mismatch:

    • Mistake: Using a custom alias (e.g., planItem) in queries but not defining ->as('planItem') in the relationship.
    • Fix: Ensure consistency between the alias in the relationship and queries.
  4. N+1 Queries Persist:

    • Mistake: Forgetting to eager-load pivot relations in nested queries.
    • Fix: Always include pivot relations in with() clauses for BelongsToMany relationships.
  5. Laravel Version Mismatch:

    • Mistake: Using the package with Laravel <8 or PHP <8.1 (as of v2.0.0).
    • Fix: Upgrade to Laravel 8+ and PHP 8.1+ for compatibility.

Debugging Tips

  1. Verify Eager-Loading: Use Laravel Debugbar or toSql() to confirm queries:

    $query = Plan::with('items.pivot.unit')->toSql();
    dd($query); // Check generated SQL
    
  2. 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
    }
    
  3. Inspect Relationships: Dump the relationship definition to debug:

    dd($plan->items->getRelation()); // Check if pivot relations are loaded
    
  4. 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();
    

Performance Tips

  1. 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();
    
  2. Limit Nested Relations: Avoid deeply nested pivot relations (e.g., pivot.unit.category.subcategory) unless necessary, as they increase query complexity.

  3. 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);
    });
    
  4. 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();
    

Extension Points

  1. Customizing the Trait: Override the trait’s getEagerLoads() method to modify eager-loading behavior:
    class Plan extends Model
    {
        use EagerLoadPivot
    
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.
codraw/entity-migrator
codraw/doctrine-extra
codraw/aws-tool-kit
codraw/validator
codraw/workflow
codraw/open-api
codraw/cron-job
codraw/process
codraw/log
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony