laravel eloquent - how to select users - laravel

I've got two tables:
Users table with id's
And Messages table with users's Id as "from" and offer Id's as offer_id
I want to select all users that send messages to with certain offer_id's
For example
user with id 1 send few messages:
Id from offer_id
1. 1, 5
2. 2, 5
3. 1, 5
4. 1, 3
I want to select all users that sent offer_id =5, so users of id 1 and 2
How to do it via eloquent with Message and User class?
I've got offer_id given so I can easily select messages:
$messages = Message::where('offer_id', $id);
but how to select users?
edit:
I tried this way:
in Message model:
public function fromContact()
{
return $this->hasOne(User::class, 'id', 'from');
}
and then in controller:
$contacts = $messages->fromContact;
but it gives an error
edit migration:
Schema::create('messages', function (Blueprint $table) {
$table->increments('id');
$table->integer('from')->unsigned();
$table->integer('to')->unsigned();
$table->boolean('read')->default(false);
$table->integer('offer_id')->nullable();
$table->mediumText('body')->nullable();
$table->timestamps();
});
and error:
"message": "Undefined property:
Illuminate\\Database\\Eloquent\\Builder::$fromContact",

You're missing some closure on your Query Builder. When you use
$messages = Message::where('offer_id', $id);
You don't have an instance of Message until you use a Closure, which are ->first(), ->get(), etc. So using
$message->fromContact;
Will result in an error stating that ->fromContact is not available on a Builder instance. To make this work, use
$messages = Message::where('offer_id', $id)->get();
foreach($messages AS $message){
$contacts = $message->fromContact;
}
Since this doesn't have much context in a loop, the code above doesn't do anyhting, but $messages->fromContact would also be an error. To get around that, use:
$message = Message::where('offer_id', $id)->first();
$contacts = $message->fromContact;
That should give you a good idea of what's going wrong and how to handle it.
Edit
When looping over multiple Message instances, push $message->fromContact to an array (or Collection) for use later:
$messages = Message::with(['fromContact'])->where('offer_id', $id)->get();
// Note: Using `::with()` prevents additional database calls when using `$message->fromContact` in a loop.
$contacts = []; // or $contacts = collect([]);
foreach($messages AS $message){
$contacts[] = $message->fromContact; // or $contacts->push($message->fromContact);
}

Related

How to query for the messages between the exact recipients?

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!

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();

Laravel: How to get data from 3 tables with relationship

I have 3 Tables:
Customers
id
name
Sales
customer_id
sale_date
Contacts
customer_id
contact_date
There aren't any update operations in the contacts table. Each process opens a new record in the contacts table. So, a user can have more than one records in the contacts table.
Here are my relations in models:
Customer
public function contacts()
{
return $this->hasMany(Contact::class);
}
public function sales()
{
return $this->hasMany(Sale::class);
}
Contact
public function customer()
{
return $this->belongsTo('App\Customer', 'customer_id');
}
Sale
public function customer()
{
return $this->belongsTo('App\Customer');
}
I would like to have the latest record of the contacts table and make it join with the other related tables.
Here is the query which I have tried:
$record = Contact::groupBy('customer_id')
->select(DB::raw('max(id)'));
$result = Customer::query();
$result->where('is_active', 'YES');
$result->with('sales');
$result->whereHas('contacts', function ($q) use($record){
return $q->whereIn('id', $record)->where('result', 'UNCALLED');
});
return $result->get();
In the blade file, I get some result in foreach loops. However, I am unable to get the related data from the sales and contacts table.
#foreach($result as $item)
#foreach($item->sales as $sale) // Has no output and gives error: Invalid argument supplied for foreach()
#foreach($item->contacts as $contact) // Has no output and gives error: Invalid argument supplied for foreach()
Can anyone help me how to display the sale and contact date? Or any idea for how to improve this code quality?
If you want the latest record of the contacts you can declare another relationship on the Customer model, e.g.:
public function latest_contact()
{
return $this->hasOne(Contact::class)->latest('contact_date');
}
BTW you can always declare one or more hasOne additional relationship if you have a hasMany in place the foreign key used is the same.
In this way you can retrieve latest_contact eager loaded with your Customer model:
$customer = Customer::with('latest_contact')->find($id);
Or use this relationship in your queries, something like that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->whereHas('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
Or that:
$customers = Customer::where('is_active', 'YES')
->with('sales')
->with('contacts')
->with('last_contact', function ($q){
return $q->where('result', 'UNCALLED');
})->get();
If you want you can declare last_contact with the additional where:
public function latest_contact()
{
return $this->hasOne(Contact::class)
->where('result', 'UNCALLED')
->latest('contact_date');
}
This way all other queries should be easier.
I hope this can help you.
I'm not sure, but can you try to do the following:
return Customer::where('is_active', 'YES')
->with([
'sale',
'contact' => function ($query) use($record) {
return $query->whereIn('id', $record)->where('result', 'UNCALLED');
}
])->get();

Laravel 5.6 - Using model functions in ModelFactory

I am working with Laravel 5.6 and found myself a weird problem while extending the functionality of my project.
Right now i need to create two new models: order and item. It was quite easy to fill the items table with dummy data using Faker and Laravel Factories/Seeders. The biggest problem is while working with the order model.
This little fellow is related to a company with a foreign key named company_id and user with a foreign key named seller_id. The company field is okay, the trouble is behind my seller_id
This seller needs a role related to the company my factory will randomly pick for it because the user is not related to the company (directly) and i can't just look for it with a company_id.
In order to get all the users "related" to my company, i've created the next function on my Company model:
public function users()
{
$roles = $this->roles;
$users = [];
foreach ($roles as $role) {
foreach ($role->users as $user) {
$user->makeHidden(['pivot']);
array_push($users, $user);
}
}
$users = array_unique_objects($users);
return $users;
}
btw: I'm using laravel-permissions, a library made by Spatie.
What this functions does is get every role from a company and then it pushes it to an array of users.
This custom helper: array_unique_objects tracks any repeated user on my array and removes them.
That function works find because i've tested on a couple of controllers so i know there is no problem with it. Either way, my OrderFactory.php looks like this:
<?php
use Faker\Generator as Faker;
use App\Models\User;
use App\Models\Company;
$factory->define(App\Models\Order::class, function (Faker $faker) {
$company = Company::get()->random(1);
$users = $company->users();
$user = array_random($users);
return [
'company_id' => $company,
'seller_id' => $user->id,
'code' => strtoupper(str_random(10)),
'description' => $faker->sentence($nbWords = rand(2, 4), $variableNbWords = true),
'created_at' => $faker->dateTimeBetween($startDate = '-1 year', $endDate = 'now', $timezone = null)
];
});
But when i run the php artisan db:seed command, it throws the next error in console:
BadMethodCallException : Method Illuminate\Database\Eloquent\Collection::users does not exist.
at >/home/ironman/Documentos/Sandbox/Proventas/Backend/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:99
95| */
96| public function __call($method, $parameters)
97| {
98| if (! static::hasMacro($method)) {
99| throw new BadMethodCallException(sprintf(
100| 'Method %s::%s does not exist.', static::class, $method
101| ));
102| }
103|
Exception trace:
1 Illuminate\Support\Collection::__call("users", [])
/home/ironman/Documentos/Sandbox/Proventas/Backend/database/factories/OrderFactory.php:10
2 Illuminate\Database\Eloquent\Factory::{closure}(Object(Faker\Generator), [])
/home/ironman/Documentos/Sandbox/Proventas/Backend/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:274
Please use the argument -v to see more details.
Is there anything I can do to fix this problem? I know that using Laravel Relationships will fix my problem but the specifications of this project says that i have to keep things just as the are.
Your call to
$company = Company::get()->random(1);
does not return a single company. It returns a Collection, which does not have a users dynamic function. Try
$company = Company::get()->random(1)->first();

Refactor 3 parent child category relation queries with whereHas or another type of query?

So I have categories and channels table with a following relationship. A category hasMany channels. What I'm trying to do is get all channels that belong to a parents sub categories. I have one working attempt (Attempt 2 in controller) at this time and am wondering if I could make that into one query?
Channel Categories
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->integer('parent_id')->default(null);
Channels
$table->increments('id');
$table->string('name');
$table->string('slug');
$table->integer('category_id');
Get All Channels By Category Slug Route
Route::get('/{channelCategory}', 'ChannelController#index');
ChannelController
public function index($channelCategory)
{
//Attempt 1. This works perfectly fine, but would like it to be in one query if possible
/*if($channelCategory->parent_id === 0){
$categories = ChannelCategory::where(['parent_id' => $channelCategory->id])->pluck('id');
$channels = Channel::whereIn('category_id', $categories)->get();
} else {
$channels = $channelCategory->channels;
}*/
//Attempt 2 whereHas Query.
//The problem is that it gets all posts from all parent categories instead of just one.
/*$channels = Channel::whereHas('category', function ($query) use ($channelCategory) {
$query->where('parent_id', $channelCategory->parent_id);
$query->orWhere('parent_id', null);
})->get(); */
return view('channels.home', compact('channels'));
}
Maybe what I am trying to do isn't possible with a whereHas. Is it possible to do the second attempt in one query and if so how?
I think you can do this by just eager loading the channels then mapping the category channels together:
$categories = ChannelCategory::with('channels')
->where('parent_id', $channelCategory->id)
->get();
return view('channels.home', [
'channels' => $categories->flatMap->channels
]);
Pagination would likely need to be done manually using the LengthAwarePaginator class:
$page = $request->get('page', 1);
$perPage = $request->get('perPage', 15);
$channels = $categories->flatMap->channels;
$items = $channels->forPage($page, $perPage);
$paginator = new LengthAwarePaginator($items, $channels->count(), $perPage, $page);
Getting the latest would involve sorting the collection and taking the limit desired:
$limit = $request->get('limit', 10);
$latest = collect($categories->flatMap->channels)->sortByDesc('created_at')->take($limit);

Resources