1
votes

I am having an issue getting a hasManyThrough to work:

public function deliveryContainers() : HasManyThrough
{
    return $this->hasManyThrough(
        DeliveryContainer::class, // Final
        StockMovement::class, // Intermediate
        'product_id', // Foreign key on Intermediate
        'id', // Foreign key on Final
        'product_id', // Local key on Current
        'location_id' // Local key on Intermediate
    )->where('delivery_id', $this->delivery_id);
}

Because the stockMovements table returns multiple results, my resulting delivery containers collection contains duplicate entries. If I could somehow put a group/unique on the intermediate table query then this would be resolved.

I can get a collection with the correct deliveryContainers eager loaded using the following:

public function deliveryContainers()
{
    return $this->hasMany(StockMovement::class, 'entity_id', 'delivery_id')
        ->with('deliveryContainer')
        ->where('product_id', $this->product_id)
        ->get()
        ->unique('location_id');
}

However, to access the deliveryContainer I now have the following:

foreach($this->deliveryContainers() as $row){
    $row->deliveryContainer->id;    
}

And what I would like to have...

foreach($this->deliveryContainers() as $row){
    $row->id;
}

Is there any way to push the eager loaded relationship up a level (if that can be used to describe it), or even better add some kind of unique filter to the hasManyThrough relationship?

Table Structure

delivery_exceptions (where this relationship originates)

  • product_id
  • delivery_id

delivery_containers

  • id
  • delivery_id

stock_movements

  • entity_id (linked to delivery id)
  • product_id

Relationships

  • A delivery exception belongsTo a product
  • A product hasMany stock_movements
  • A stock movement belongsTo a delivery container
  • A delivery exception hasMany delivery containers... (indirectly, through a combination of the product and the stock movements)
2
How exactly do your models look like and what are their keys and other relationship definitions?Namoshek
Are these really all the keys and foreign keys on the delivery_containers table? Because if so, why do you need a hasManyThrough relationship at all? On delivery_exceptions, you have a delivery_id which you find again on the delivery_containers. So it looks quite straight forward to me...Namoshek
Delivery containers actually requires a relationship to the stock movement table in order to split the delivery into delivery containers. A delivery has many delivery containers and a delivery containers has many products. I am trying to find all delivery containers which a product from a delivery exists withinAdam Lambert

2 Answers

3
votes

You've got a really tough setup there and I'm not entirely sure that I got the full idea behind it (also because of you using entity_id at some place instead of delivery_id). But nonetheless, I gave it a shot.

The hasManyThrough relationship you defined looks actually not too bad, but in my opinion there is a better way to get to the result. But first let's have a look at your relationships:

                                               3
                            +-------------------------------------+
               4            v                                     |
        +-------------> Delivery <----------+                     |
        |                                   | 1                   |
        +                                   +                     +
DeliveryException +---> Product <---+ StockMovement +---> DeliveryContainer
        +                                                         ^
        +---------------------------------------------------------+
                                      2

As a StockMovement already belongs to a DeliveryContainer, which in return belongs to a Delivery, the relation from StockMovement to Delivery (marked as 1) seems obsolete to me. Anyway, to get relation 2 on your model, you can use the paths 3 and 4 to your advantage:

class DeliveryException
{
    public function deliveryContainers(): HasMany
    {
        return $this->hasMany(DeliveryContainer::class, 'delivery_id', 'delivery_id');
    }
}

Obviously, this will give you all the DeliveryContainers, unfiltered by the Product. Therefore I suggest adding a second function:

public function deliveryContainersByProduct(): HasMany
{
    return $this->deliveryContainers()
        ->whereHas('stockMovements', function ($query) {
             $query->where('product_id', $this->product_id);
        });
}
2
votes

The accepted answer is far more elegant, but this is another way to do this too:

public function deliveryContainers1()
{
    return $this->hasManyThrough(
        DeliveryContainer::class, // Final
        StockMovement::class, // Intermediate
        'product_id', // Foreign key on Intermediate
        'id', // Foreign key on Final
        'product_id', // Local key on Current
        'location_id' // Local key on Intermediate
    )
        ->where('delivery_id', $this->delivery_id)
        ->distinct();
}