TL;DR: Need latest message from each sender.
In my Laravel application I have two tables:
Users:
- id
- name
Messages:
- id
- sender_id
- recipient_id
- body
- created_at
And of course models.
User model:
public function messages() {
return $this->hasMany('App\Message', 'recipient_id');
}
Messages model:
public function sender() {
return $this->belongsTo('App\User', 'sender_id');
}
public function recipient() {
return $this->belongsTo('App\User', 'recipient_id');
}
When user opens his inbox, he should see list of latest message from any other user.
So if there are messages:
id sender_id recipient_id body created_at
1, 2, 1, hi, 2016-06-20 12:00:00
2, 2, 1, hi, 2016-06-21 12:00:00
3, 3, 1, hi, 2016-06-20 12:00:00
4, 3, 1, hi, 2016-06-21 12:00:00
Then user with id 1 (recipient_id) should see only messages with id 2 and 4.
This is current solution in Users model:
return Message::whereIn('id', function($query) {
$query->selectRaw('max(`id`)')
->from('messages')
->where('recipient_id', '=', $this->id)
->groupBy('sender_id');
})->select('sender_id', 'body', 'created_at')
->orderBy('created_at', 'desc')
->get();
This is working, but I was wandering if it is possible to achieve this the Laravel way. Probably with eager loading. My Laravel skills just not enough and after several days of trying I don't have a solution.
Thanks.
Taking inspiration from this post, the most efficient way to do this would be like so:
While it is not the most Laravel friendly, it is the best solution based on performance as highlighted by the post linked in this answer above
I tried some similar approach and found out you only need to orderBy
created_at
immediately you find all the messages of theUser
then you can use Laravel'sCollection
to group them them bysender_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 asreceiver
withreceiver_id
inmessages
table, then:Then loop round the collection, and fetch the first:
You should end up with the result such as:
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 besender_id
. (ref: Laravel's unique)That is:The consequence is that we don't need the
foreach
and thegroupBy
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:Then in the controller use:
PS: Still not sure which one is optimally better than the other.
Why not simply accessing the
messages
, like this -Maybe you can try this one:
This may not be very efficient since it took two queries to do the job but you will gain benefits thru code readability.
I hope it works the way it should be. I haven't tested it though but that's how I usually do this... Cheers!
You can try this one
this may be a solution (not tested though)