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.
Related
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!
for my app I made it so an user can favorite venues, this relationship between venues and user is many to many, problem is I don't know how to get favorite_venues paginated.
Right now what I'm doing is getting user with favorite_venues but that doesn't seem right to me, is there a way to get favorite venues paginated directly without having to get the user.
What I'm currently doing:
public function getFavorites($request)
{
$user = User::with(['favoritevenues','favoritevenues.category'])->findOrFail(1);
return $user;;
}
I set relationships like this:
User Model
public function favoritevenues()
{
return $this->belongsToMany('App\Models\Venue', 'favorite_venues', 'user_id', 'venue_id')->withTimeStamps();
}
Venue Model
public function favorites()
{
return $this->belongsToMany('App\Models\User', 'favorites', 'venue_id', 'user_id')->withTimeStamps();
}
Thanks in advance
you can do:
Venue::whereHas('favorites', function ($q) {
return $q->where('id', 1);
})->paginate(5);
This will get and paginate all the venues which are the favourites of user with id 1
I am trying to get my Yajra Datatable working correctly but struggling.
Basically I want to get all clients appointments in which the client belongs to the logged in user. I then want to access the Client name and then the appointment data. I have used the following to get the appointment data
$user = User::find(Auth::user()->id);
$data = $user->clients()->with('appointments')->get()->pluck('appointments')->flatten();
return Datatables::of($data)
->make(true);
This allows me to show a row for each appointment that exists for the user-owned clients appointments. But how can I also access the clients name from this?
I have tried many different ways but if I use something like
$data = $user->clients()->with('appointments')->get();
I can access all of the data I need but it shows the existing clients in each row, not all appointments owned by the clients.
My setup is
User Model
public function clients(){
return $this->hasMany(Client::class);
}
public function appointments()
{
return $this->hasManyThrough(Appointment::class, Client::class);
}
Client Model
public function appointments(){
return $this->hasMany(Appointment::class);
}
public function users(){
return $this->belongsTo(User::class);
}
Appointment Model
public function client(){
return $this->belongsTo(Client::class);
}
Thank you
You might be looking for whereHas:
$appointments = Appointment::with('client')
->whereHas('client.users', function ($query) use ($user) {
$query->where('users.id', $user->id)
})
->get();
This translates to: "Give me all the appointments that belong to clients of a specific user".
Just a suggestion, it might be wise (not required at all) to alter your database structure to something more flexible. What if you for instance keep track in you appointment which user is present?
appointments
- user_id
- client_id
This would make your query a bit simpler
// In User
public function appointments()
{
return $this->hasMany(Appointment::class);
}
$user->appointments()->with('client')->get();
You could go even further by making the relation between User and Appointment, Client and Appointment many to many so you could have appointments with more than one user and/or client.
I'm using Laravel 5.8 to build a babysitting site. I have 4 tables with different relationships as below:
please see this image
The relationships are:
Babysitter->hasMany(session)
Sessions->hasOne(Review)
Sessions->hasOne(Kids)
Sessions->hasOne(Babysitter)
Sessions->hasOne(Parent)
I want to achieve 2 things:
First one
I want to show this result when listing all babysitters. I'm showing this information for each babysitter:
plsease see this image
See here what I couldn't achieve
plsease see this image
This is my code
Sitters::where('Status', 'active')->where('Verified', 1)->get();
Second one
Also, I've tried to show kids name with parent review as shown here:
plsease see this image
This is what i'm using
Sessions::select('Reviews.*', 'Sessions.Parent_id')->join('Reviews', 'Reviews.Session_id', '=', 'Sessions.id')->with('owner')->where('Trainer_id', session('user')->Id)->where('Status', '=', 'complete')->with('owner')->orderBy('Sessions.id', 'DESC')->get();
Here is Session.php Model
public function owner(){
return $this->belongsTo('App\Models\Parents', 'Parent_id');
}
As discussed change the relations:
Babysitter->hasMany(sesstion)
Sessions->hasOne(Review)
Sessions->belongsTo(Kids)
Sessions->belongsTo(Babysitter)
Sessions->belongsTo(Parent)
First one
in Babysitter.php declare the following attributes
class Babysitter extends Model
{
public function reviews()
{
$this->hasManyThrough(Review::class, Session::class);
}
public function getAverageReviewAttribute()
{
return $this->reviews()->avg('Rating');
}
}
Then you just need to call it on the model instance.
$babysitter = Babysitter::first();
return $babysitter->average_review;
Second one
Just use the relation
$babysitter = BabySitter::with(['sessions' => public function ($session) {
$session->with(['review','parent','kids']);
})->where('trainer_id', '=', session('user')->Id) //did not understand this condition
->first();
This assumes you have parent, kids and review relation declared on Session::class. (change the names if needed)
After a few days of searching & testing, this is what worked for me:
Inside (Sitters) Model, put this relation
public function sessions()
{
return $this->hasMany(Sessions::class, 'sitter_id')
->withCount('reviews')
->withCount(['reviews as review_avg' => function($query){
$query->select(DB::raw('AVG(Rating)'));
}]);
}
Also, inside (Sessions) Model, put this relation
public function reviews()
{
return $this->hasOne(Reviews::class, 'Session_id');
}
Now you query like this
return $sitters = Sitters::with('sessions')->get();
I hope this can help someone :)
I am using laravel eloquent. I have fetched data from two table using eloquent.
I have post table and chat table. For post table I have model Post.php and for chat table I have model Chat.php. Here is the the eloquent relation I have created to fetch chat for individual post for a user.
in Post.php
public function TeamMessage()
{
return $this->hasMany('App\Chat','post_id');
}
And in Chat.php
public function ChatRelation()
{
return $this->belongsTo('App\Post');
}
it is working perfect. But this relation fetch all messages for a specific post. I want to fetch all unread message from chat table. I have a column named unread in chat table.
Now my question is how I can fetch only unread message for a specific post.
While the other answers all work, they either depend on scopes (which are very useful in many circumstances) or on you having already instantiated an instance of $post, which doesn't let you eager load multiple posts with their messages.
The dynamic solution is this, which will let you fetch either 1 or more posts and eager load their messages with subquery:
$posts = Post::with(['TeamMessage' => function ($query) {
$query->where('unread', true); // This part applies to the TeamMessage query
}])->get();
See in documentation
Edit:
If you, however, want to filter the posts, to only show those that have unread messages, you need to use whereHas instead of with:
$posts = Post::whereHas(['TeamMessage' => function ($query) {
$query->where('unread', true); // This part applies to the TeamMessage query
}])->get();
More in the documentation.
You can also chain whereHas(...) with with(...).
For querying relationships, you have to call them as functions instead of properties, like this:
$unreadPosts = $post->TeamMessage()->where('unread', true)->get();
For more information on this you can take a look at the docs.
You need to create a local scope on your model, information on local scopes can be found here: https://laravel.com/docs/5.6/eloquent#local-scopes
public function scopeUnread($query)
{
return $query->where('unread', 1);
}
Then in your controller/view
$unread = $yourmodel->unread()
First I would change your relation names to the name of the entity in lower case:
in Post.php
public function chats()
{
return $this->hasMany('App\Chat','post_id');
}
And in Chat.php
public function post()
{
return $this->belongsTo('App\Post');
}
public function scopeUnread($query)
{
return $query->where('unread', 1);
}
Then you can use
$post->chats()->unread()->get();