framework: I can't eager load a pivot table relation

  • Laravel Version: 9.17.0
  • PHP Version: 8.1.0
  • Database Driver & Version: MySQL - v8.0.27

Description:

Hi everyone

My issues is that i can’t eager load a pivot relation only lazy loading is available.

For example i have the following tables

Products

id name barcode_type barcode unit_id cost price
1 Product 1 code128 000000000001 1 10 12

Purchases

id warehouse_id supplier_id status date reference_no note total
1 1 1 received 2022-06-17 17:19:34 NULL NULL 120

Units

id unit_id name code multiplier
1 NULL Piece Pcs 1
2 1 Dozen - 12 D-12 12

And now the pivot table

product_purchase

purchase_id product_id unit_id quantity cost subtotal
1 1 2 1 10 120

Models

// Product Model
public function purchases()
{
    return $this->belongsToMany(Purchase::class, 'product_purchase', 'product_id', 'purchase_id')
        ->withPivot('unit_id', 'quantity', 'cost', 'subtotal')
        ->using(ProductPurchase::class);
}

public function unit()
{
    return $this->belongsTo(Unit::class, 'unit_id', 'id');
}


// Purchase Model
public function products()
{
    return $this->belongsToMany(Product::class, 'product_purchase', 'purchase_id', 'product_id')
        ->withPivot('unit_id', 'quantity', 'cost', 'subtotal')
        ->using(ProductPurchase::class);
}


// ProductPurchase Model that extends Pivot
public function unit() // I need to eager load this
{
    return $this->belongsTo(Unit::class, 'unit_id', 'id');
}

Note that i want the unit which the product was purchased with not the default product unit

I want to eager load the unit like this.

This will throw that pivot relation does not exist.

$purchase = Purchase::with(['products.pivot.unit'])->first();

But i can access it via lazy loading like so:

$purchase = Purchase::with(['products'])->first();

foreach($purchase->products as $product) {
    info($product->pivot->unit);
}

But this will produce N+1 queries

This is the desired output for $purchase

{
    "id": 1,
    "warehouse_id": 1,
    "supplier_id": 1,
    "status": "received",
    "date": "2022-06-17 07:16:35 AM",
    "reference_no": null,
    "total": 120,
    "note": null,
    "created_at": "2022-06-17 10:53:46 AM",
    "updated_at": "2022-06-17 06:51:27 PM",
    "products": [
        {
            "id": 1,
            "name": "Product 1",
            "barcode_type": "code128",
            "barcode": "000000000001",
            "unit_id": 1,
            "cost": 10,
            "price": 12,
            "pivot": {
                "purchase_id": 1,
                "product_id": 1,
                "unit_id": 2,
                "quantity": 1,
                "cost": 10,
                "subtotal": 120,
                "unit": {
                    "id": 2,
                    "unit_id": 1,
                    "name": "Dozen - 12",
                    "code": "D-12",
                    "multiplier": 12
                }
            }
        }
    ]
}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

So you want to use a 3-way pivot table. As your pivot table have keys for products, purchases and units.

I am assuming this from your reply to @ankurk91 , as you said eager loading products.unit would not load the correct unit, as you expect the unit recorded on the product_purchase table to be loaded, not the one from products table.

Laravel does not support this out of the box.

You can try to write a custom relation, or add a join statement to your relation definition. Or to eager load after first retrieval:

$purchases = Purchase::with(['products'])->get();

$purchases->flatMap(fn ($purchase) => $purchase->products)
    ->pluck(`pivot')
    ->pipeInto(\Illuminate\Database\Eloquent\Collection::class)
    ->load('unit');

\dd($purchases->toArray());

for a single purchase:

$purchase = Purchase::with(['products'])->first();

$purchase->products
    ->pluck(`pivot')
    ->pipeInto(\Illuminate\Database\Eloquent\Collection::class)
    ->load('unit');

\dd($purchase->toArray());

This will only work due to you have a unit relation defined in your pivot model.

Further helping you with this would be beyond the scope of this issue tracker, so I advise you to seek help in the support channels as outlined on the documentation:

https://laravel.com/docs/9.x/contributions#support-questions

EDIT: Added single purchase example, and ->pipeInto(...) call to an Eloquent Collection as ->pluck(...) returns a base Collection.

@rodrigopedra

I only knew that after a lot of looking, it would be better if it was stated in the docs as warning. First I thought it was a bug but it wasn’t.

Next time I will put it on a support forum, Sorry for the mistake.

@jihad28 as you already knew this was an unsupported feature and not a bug, please next time open a discussion or seek help in the support forums as it is stated in the docs.

This issue tracker is for bugs only.