How to query for the messages between the exact recipients? - laravel

Is there a similar "whereIn" for "whereInBetween" in Laravel 7+? I have a basic chat app:
Message model
id, content, userId, threadId
threadId is filled when a user replies to a message. Think "parentId".
public function sentBy(): BelongsTo
{
return $this->belongsTo(User::class, 'userId');
}
public function recipients(): HasMany
{
return $this->hasMany(MessageRecipient::class);
}
Recipients model
id, userId, messageId
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'userId');
}
public function message(): BelongsTo
{
return $this->belongsTo(Message::class, 'messageId');
}
I could then say:
User::first()->messages;
Message::first()->sentBy;
With this setup, the recipients model could easily be called "group". When a user creates a message, a recipients relationship is created for the current user along with other users regardless if the message was sent to the one who created the message. Common? Ok. My issue is this, I need to return all conversation between us three userIds: [1,2,3]:
$messageId = 1;
// Message with id of 1 will not have a threadId, only its replies.
$userIds = [1,2,3];
foreach($userIds as $id) {
Recipient::create(['messageId' => $messageId, 'userId' => $id]);
}
We have a "group chat theme" going on here. If I need to query for message with us three users (above userIds), I'd do:
return Message::whereHas('recipients', function(Builder $q) use ($userIds) {
return $q->whereIn('userId', $userIds);
})
->orderBy('id', 'DESC')
->get();
Perfect. Exactly what I need. Now, let's create another message:
$messageId = 2;
$userIds = [1,2];
$queryUserIds = [2,3];
foreach($userIds as id) {
Recipient::create(['messageId' => $messageId, 'userId' => $id]);
}
// With this query, I'd expect to see message between the two queryUserIds **only**,
// Expected results should be an empty array because we have no message between userId 2 and 3 only. We have messages between all three: 1,2,3.
return Message::whereHas('recipients', function(Builder $q) use ($queryUserIds) {
return $q->whereIn('userId', $queryUserIds);
})
->orderBy('id', 'DESC')
->get();
How can I query messages between a set of userIds. Not sure I'm expressing myself correctly. All Im saying is, for users 1, 2 and 3, give me conversations with only those three users and nothing else because only those three are in the same conversation.
I got this idea from Facebook's messenger. I don't know how they do it but I'm "borrowing" a functionality; when you create a new message, it seems to retrieve the last/latest conversations between same "recipients". I know there are more complexities that go on but I wanted to do something simple.

Think I have found it. Correct me if I'm wrong. To recap, when a message is created, it creates an array of recipients with a message.threadId of null. When anyone replies to that message, you'll now have a message.threadId of 1. When I want messages between all three users: 1,2,3, I'd query:
$userIds = [1,2,3];
// Probably could use sql's sum but... 🤷🏽
$sumIds = array_sum($userIds);
$messages = Message::whereHas('recipients', function(Builder $q) use ($sumIds) {
// SUM is the key part.
return $q->havingRaw('SUM(recipients.userId) ='. $sumIds);
})->get();
This works 99% but I want the "replies", messages with threadId not null; I need the replies:
$message = Message::whereHas('recipients', function(Builder $q) use ($sumIds) {
return $q->havingRaw('SUM(recipients.userId) ='. $sumIds);
})
// I literally need the first in the array. I'd expect the array should contain one object and never more than one
->first();
if (!$message) {
return [];
}
// Another query to get all (conversations). You'll now see the picture:
return Message::where(function($q) use ($message) {
$q->orWhere('id', $message->messgeId);
// This gives me the "replies"
$q->orWhere('threadId', $message->messgeId);
})->get();
This works for now. If anyone could refactor, I'll gladly accept your answer with an upvote.
Thanks!

Related

get posts where relationship count is equel to post's attribute

I have a model "Post" which has a field "daily" in the database.
$table->integer('daily'); // could be anything
The Model "Post" has a relationship to the model "Comments".
public function comments() {
return $this->hasMany(Comment::class);
}
public function completed() {
return $this->hasMany(Comment::class)->completed();
}
Comments model
public function scopeCompleted($query) {
return $query->....?
}
I want to get all user's posts where it's comments count are equal to the post's 'daily' field. For example: if the post's daily field is '5' and there are 5 comments to this posts, I want to see this post in the returned lists, otherwise don't return it.
I know I can get all of them and do it with loop, but I wanna know if there is any way to do this with eloquent only.
Try this
$user=User::with('posts','posts.comments')
->whereHas('posts.comments',function ($query){
//if any additional filters
},'=',DB::raw('posts.daily'))
->get();
or if not needed additional query then
$user=User::with('posts','posts.comments')
->whereHas('posts.comments',null,'=',DB::raw('posts.daily'))
->get();
Got it working like this
public function scopeCompleted($query) {
return $query->has('comments', '=', DB::raw('posts.comments'));
}

Fetch users with their unread messages with Laravel Join vs Laravel Eloquent

I 'm working on a chat application. I am fetching all the users to show in the sidebar. With each user fetched I also want to show an indication of unread messages from any user. I have the messages table like this.
id | user_id | receiver_id | message | is_read
and standard users table of Laravel. I achieved it with the following join in the the controller method.
public function users_join()
{
// get logged in user
$user = Auth::id();
// left join to get all users all time
return DB::table('users')->where('users.id', '!=', $user)
->leftJoin('messages', function ($join) use ($user) {
// first evaluate where in below function
$join->on('users.id', '=', 'messages.user_id')
// filter messages sent to current user
->where('messages.receiver_id', '=', $user)
// get only those messages which are unread
->where('messages.is_read', '=', 0);
})->select('users.*', 'messages.user_id', 'messages.receiver_id', 'messages.message'
, 'messages.is_read')
// finally grouped by email to get only 10 records from users table
->orderBy('messages.created_at', 'DESC')->groupBy('email')->get();
}
Now I want to do it with the Laravel Eloquent. I can't think of any other way of doing this without using loop. Here is the code for that.
public function users_eloquent()
{
// get current user
$current_user = Auth::id();
// each through all users except current
$users = User::all()->except($current_user)->each(function ($user) use ($current_user) {
// laravel relationship
$user->messages = $user->messages()
// messages sent to me
->where('receiver_id', $current_user)
// unread message sent to me
->where('is_read', 0)
// get latest unread message
->orderBy('created_at', 'DESC')
// first latest message
->first();
});
// send back the response
return $users;
}
My User Model is like this
public function messages()
{
return $this->hasMany('App\Message');
}
and just in case, my Message Model is like this
public function user()
{
return $this->belongsTo('App\User');
}
I want to achieve it with Eloquent without using each loop. How can I do this?
I have all the users in sidebar and their unread mark. Below is the attached screenshot.
Thanks!
You don't need to loop all the users.
You can find the user except current_user by where() method on Eloquent model before it become collection.
And use with() to get the latest msg for those users:
$current_user = Auth::id();
return User::where('id', '<>', $current_user)
->with(['messages' => function($q) use ($current_user) {
$q->where('receiver_id', $current_user)
->where('is_read', 0)
->latest()
->first();
}])->get();

How to get the last row in with method relationships in laravel?

I'm trying to build a chat application with laravel and I have two tables one for the Conversations and another one for the Messages, and I want to have each conversation with the last message. This is the query that I tried:
$allConvs = Conversation::with(['messages' => function ($query) {
$query->latest()->first();
}])
->get();
But it returns the last message for the the first conversation only and null for the others. Is there any other way to do it?
This is my relationships
Conversation.php
public function messages(){
return $this->hasMany('App\Message', 'conversation_id','id')->latest();}
Message.php
public function conversation()
{
return $this->belongsTo('App\Conversation' ,'id', 'conversation_id');
}
You can use a HasOne relationship:
public function latestMessage() {
return $this->hasOne(Message::class)->latest();
}
$allConvs = Conversation::with('latestMessage')->get();
This will still fetch all messages in the background, but only show the latest one for each conversation.

Return first element from relation with `Lazy Eager Loading` [Laravel 5.2]

I have relation like this:
public function message()
{
return $this->hasMany('Engine\Message');
}
inside my Conversation model.
and for each conversation I need to get last message.
Here is what I tried, but this will get only one message for first conversation but will not get message from other conversations...
$con = Conversation::all();
$con->load(['message' => function ($q) use ( &$mess ) {
$mess = $q->first();
}]);
return $con;
I don't wana query for each record... Anyone know how to solve this problem?
As suggested here!
Don't use first() or get() in eager loadings you should create a new relationship in the model.
The model would look something like this...
public function message()
{
return $this->hasOne('Engine\Message');
}
kudos to 'pmall'
Try
$con = Conversation::all();
$con->load(['message' => function ($q) use ( &$mess ) {
$q->orderBy('created_at', 'desc')->take(1);
// or if you don't use timestamps
// $q->orderBy('id', 'desc')->take(1)
}]);
return $con;

Laravel / Eloquent: Search for rows by value in polymorphed table

I'm stuck at the moment and hope someone can give me a hand. I'm using a polymorphic relation and want to search my database for rows that fulfill conditions in the "parent" and the "child" table.
To get concrete, one small example. Given the following structure I e.g. want to look for a property with price "600" and rooms "3". Is there a way to do that with eloquent?
Tables
Table properties (parent)
id
price
details_type [can be "Apartment" or "Parcel"]
details_id
Table apartments (child)
id
rooms
Table parcels (child)
id
... (does not have a "rooms" column)
Relationships
Class Property
public function details() {
return $this->morphTo();
}
Classes Apartment + Parcel
public function property() {
return $this->morphMany('Property', 'details')
}
What I tried
A lot, really. But somehow I'm always doing something wrong or missing something. The solutions that, in my opinion should work are either:
Property::with(array('details' => function($query) {
$query->where('rooms', 3);
}));
or
Property::with('details')
->whereHas('details', function($query) {
$query->where('rooms', '=', '3');
});
But in both cases I get the following FatalError.:
Class name must be a valid object or a string
Has anyone of you already had a similar problem? Thank you very much for any kind of hint.
Let's start with your naming convention:
public function detail() // It relates to a single object
{
return $this->morphTo();
}
And
public function properties() // It relates to several objects
{
return $this->morphMany('Property', 'details')
}
Then you would be able to do this:
$properties = Property::whereHas('details', function($q)
{
$q->where('rooms', '=', '3');
})
->where('price', '=', 600)
->get();
Please note that this will never return a Parcel, since there isn't a parcel with a room.

Resources