Laravel Eloquent - Merging two eloquent model results (not the collections)
Asked Answered
V

3

6

I have a table of stock movements, which shows a history of a product (receiving, moving location etc.)

I have two queries which calculate (via aggregate sums):

  1. The original received qty.
  2. The current live qty (after things have been moved).

Both of these queries return a collection of the same models. They will always have the same number of results in each, and the product_id's will always be the same. The only part that is different is the received/live qty and I want to somehow merge the individual records into a new collection. i.e.

Single record from query 1:

0 => StockMovement {#1602 ▼
  #original: array:2 [▼
    "live_qty" => "8"
    "product_id" => "2606598e-4150-461a-9e91-10d67cce8daa"
  ]
}

Single record from query 2;

0 => StockMovement {#1602 ▼
  #original: array:2 [▼
    "received_qty" => "15"
    "product_id" => "2606598e-4150-461a-9e91-10d67cce8daa"
  ]
}

I am trying to end up with a merged result which looks like the following:

0 => StockMovement {#1602 ▼
  #original: array:3 [▼
    "received_qty" => "15"
    "live_qty" => "8"
    "product_id" => "2606598e-4150-461a-9e91-10d67cce8daa"
  ]
}

I want to do this in a way that does not convert the Product object to an array, as I need the relationships that are embedded within it.

Currently, I have hacked this as follows:

$live = $this->liveQtyCollection();
$merged = $this->receivedQtyCollection()->map(function(StockMovement $received) use($live){
    $line = $live->where('product_id', $received->product_id)->first();
    return arrayToObject(array_merge($received->toArray(), $line->toArray()));
});
return $merged;

The arrayToObject function, recursively converts the array back into an object so I can use the arrow selector in the same way as when it was its original collection, but I am sure there must be a better way!

I have tried:

  • Merge
  • Union
  • All

My end goal is to be able to use the resulting collection as follows:

@foreach($stockMovements as $stockMovement)
    {{ $stockMovement->id }}
    {{ $stockMovement->live_qty }}
    {{ $stockMovement->received_qty }}
@endforeach
Vesiculate answered 17/4, 2019 at 19:46 Comment(4)
"All multi-result sets returned by Eloquent are instances of the Illuminate\Database\Eloquent\Collection object". Why did you say not collections?Laws
I guess I am thinking of a collection as a "collection of eloquent model results". I want to make sure that I do not break the stockMovement model into an array or plain collection i.e. I want to keep all of its relationship functionality such as $stockMovement->product->title where a stockMovement belongs to a product, through the product_idVesiculate
I think you may try with this way: laravel.com/docs/5.8/eloquent-relationshipsAncestress
@Ancestress thanks. I know what a relationship is already. I am just trying to merge the two above queries while keeping the relationships intact. If I convert to array before merging, then the relationships are lost.Vesiculate
L
8

You can use this code:

$collection = collect();
$cars = Car::all();
$bikes = Bike::all();

foreach ($cars as $car)
    $collection->push($car);

foreach ($bikes as $bike)
    $collection->push($bike);

Source

Laws answered 17/4, 2019 at 20:38 Comment(0)
E
1

I know you mentioned in your question that you did not mean collections and also that you had already tried merging, but for the sake of discussion I thought I'd throw my answer in the mix, as this worked precisely for what I needed; merging the responses of two model calls.

$quote = \App\AllQuotes::withoutGlobalScopes()->find($quote_id);
$settings = \App\Settings::findOrFail(1);
$settings = collect($settings);
$quote = collect($quote);
$merged = $quote->merge($settings);

return $merged;

You have to first encapsulate the model responses as collections with the collect helper, then you can merge them together. The output then treats it as a logical join, maintaining the data from both models in a singular output.

Ence answered 4/12, 2020 at 2:54 Comment(0)
D
0

You could do something like this:

$live = $this->liveQtyCollection()->keyBy('product_id')
$merged = $this->receivedQtyCollection()->map(function(StockMovement $received) use($live){
    $received->live_qty = $live->get($received->product_id)->live_qty ?? null;
    return $received;
});
Donegan answered 17/4, 2019 at 21:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.