TL;DR: Need latest message from each sender.
In my Laravel application I have two tables:
Users:
Messages:
I tried some similar approach and found out you only need to orderBy created_at
immediately you find all the messages of the User
then you can use Laravel's Collection
to group them them by sender_id
. So to my understanding, the following approach should work, i.e give you the last message receive by the user from the senders :
So assuming you have an Authenticated User
as $user
which in this context is the same as receiver
with receiver_id
in messages
table, then:
$messages = $user->messages()
->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->groupBy('sender_id'); //this is collections method
Then loop round the collection, and fetch the first:
$result = new \Illuminate\Database\Eloquent\Collection ();
foreach ($messages as $message){
$result->push($message->first()); //the first model in the collection is the latest message
}
You should end up with the result such as:
Illuminate\Database\Eloquent\Collection {#875
all: [
App\Message {#872
sender_id: "2",
body: "hi",
created_at: "2016-06-21 12:00:00",
},
App\Message {#873
sender_id: "4",
body: "hi",
created_at: "2016-06-21 12:00:00",
},
]
PS: A note is that I can't say how efficient this might be but one thing is for sure, is the limited number of query on the db.
Hope it helps :)
UPDATE:
I will break it down as I tried it. ~
It fetches all records of messages
that belongs to user into a collection (already ordered by created_at) then, using laravel's groupBy() you have a result like the example given in that doc.
This time I didnt convert to Array. Instead, its a collection Of Collections. like collection(collection(Messages))
Then you pick the first Model at each index. The parameters I already passed into the get()
method ensures only those fields are present (i.e ->get(['sender_id', 'body', 'created_at'])
. This is is totally different from mysql groupBy(), as it does not return one row for each group rather, simply groups all records by the given identifier.
ANOTHER UPDATE
I discovered later that calling unique()
method on the resulting ordered messages would actually do the job, but in this case the unique identifier would be sender_id
. (ref: Laravel's unique)That is:
$messages = $user->messages()
->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->unique('sender_id'); //unique replaces `groupBy`
The consequence is that we don't need the foreach
and the groupBy
we have the result here.
One other way to avoid repeatition (of the above) if this is needed in more than one place is to use Laravel's query scope i.e in Message
model we can have something as this:
public function scopeLatestSendersMessages($query)
{
return $query->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->unique('sender_id');
}
Then in the controller use:
$messages = $user->messages()->latestSendersMessages();
PS: Still not sure which one is optimally better than the other.