WhereHas on a polymorphic relationship - laravel

I have the following table:
table logs
| id | loggable_id | loggable_type | action |
loggable_type could be Contract, User or Card
I need to get ALL records from the logs table, but I have to run whereHas on each of the loggable_types, so something like that:
$logs = Log::whereHas('contract', function() {
})
->whereHas('user', function() {
})
->whereHas('card', function() {
});
But I'm not sure how to define each of those relationships. Right now I only have:
public function loggable()
{
return $this->morphTo();
}
It's Laravel 5.6 if it matters.

use this
Comment::whereHasMorph('loggable', [User::class, Card::class], function ($query) {
$query->where('title', 'foo');
})->get();

Related

Laravel get users by state using city id

I have users table where has city_id and each city in cities table has state_id. How I can get users using city_id from any state?
Here is my code:
$users = User::role('company')
->when((int) $state, function ($query, $state) {
// here query
})
->when((int) $city, function ($query, $city) {
return $query->where('city_id', $city);
})
->get();
I tried:
$users = User::role('company')
->when((int) $state, function ($query, $state) {
$cities = City::where('state_id', $state)
->pluck('id')
->toArray();
return $query->whereIn('city_id', $cities);
})
->when((int) $city, function ($query, $city) {
return $query->where('city_id', $city);
})
->get();
It's work but how to do this query correctly without make new query with City model?
have you built the relation between user and city? it should be something like:
public function city()
{
return $this->belongsTo(City::class, 'city_id');
}
you can use that relation to get users where has a city that belongs to that state:
$users = User::role('company')
->when((int) $state, function ($query)use ($state) {
return $query->whereHas('city',function ($query)use ($state){
$query->where('state_id',$state);
});
})
->when((int) $city, function ($query)use($city) {
return $query->where('city_id', $city);
})
->get();
You can use Laravel HasManyThough
given that you have a state, each state has many cities, each city has many users
then in the state model, you can add this function
public function users()
{
return $this->hasManyThrough(User::class, City::class);
}
this function will return all the users in a given state
example:
$state = State::first();
$users = $state->users;
for further info read the docs

Where clause in polymorphic relationship

I have the tables
threads
- id
replies
- id
- repliable_id
- repliable_type
I want to add another column to the Thread, which is the id of the most recent reply.
Thread::where('id',1)->withRecentReply()->get()
And the following query scope
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->whereHasMorph('repliable', ['App\Thread'], function ($q) {
$q->where('repliable_id', '=', 'threads.id');
})->latest('created_at')
->take(1),
]);
}
I have also tried
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->where('repliable_id', '=', 'threads.id')
->latest('created_at')
->take(1),
]);
}
But in both cases the
recent_reply_id => null
If instead of threads.id i enter an integer, it works and the recent_reply_id is not null
For example
public function scopeWithRecentReply() {
return $query->addSelect([
'recent_reply_id' => Reply::select('id')
->whereHasMorph('repliable', ['App\Thread'], function ($q) {
$q->where('repliable_id', '=', 1);
})->latest('created_at')
->take(1),
]);
}
My question is
Is there a way to be able to fetch the recent_reply_id using the respective threads.id ?
I would suggest using appends instead of query scopes so in your Thread model we will add
protected $appends = ['recent_reply_id'];
.....
public function replies () {
// you need to add here your relation with replies table
}
public function getRecentReplyIdAttribute () {
return $this->replies()->latest('id')->first()->id;
}
now wherever you query the threads table you will have access to recent_reply_id like
$thread = Thread::where('id',1)->withRecentReply()->get();
$thread->recent_reply_id;

Laravel "whereHas" with "take"

I'm having a relationship:
employees: id | name
employments: id | employee_id | company_id | start_date | end_date | termination_type_id
One employee can have many employments through time. I need to get employees who are employed based on the last/one row in employments and that is whereHas fails me. I've set this in my model Employee.php:
In Employee.php model:
public function latestEmployment() {
return $this->hasMany(Employment::class)->orderBy('start_date', 'DESC')->take(1);
}
On filtering:
$employees->where(function ($query) {
$query->whereHas('latestEmployment', function($subquery) {
$subquery->whereNull('termination_type_id');
}
});
Looks to me that I need somewhere some eager loading because ->take(1) doesn't work that way. Here whereHas takes into account the whole table, no matter what I write next.
Thanks for help.
EDIT:
Doing first(), get(), etc. executes the query in the middle so I cannot use that. Important thing is that the whole query needs to be a Builder because I need a paginate() at the end.
The easiest solution is getting all employees and filtering them afterwards:
public function latestEmployment() {
return $this->hasOne(Employment::class)->orderBy('start_date', 'DESC');
}
Employee::with('latestEmployment')->get()
->where('latestEmployment.termination_type_id', null);
A query that only fetches the relevant employees:
Employee::select('employees.*')
->join('employments', 'employees.id', 'employments.employee_id')
->whereNull('termination_type_id')
->where('start_date', function($query) {
$query->selectRaw('max(start_date)')
->from('employments')
->whereColumn('employee_id', 'employees.id');
})->get();
Try Something like this
Employee::select('employees.*')->join('employments', function ($join) {
$join->on('employees.id', '=', 'employments.employee_id')
->whereNull('emails.termination_type_id');
})->distinct()->paginate(15);

Advanced Filters in laravel

In my case I'm working on search training institutes based on institute name,location and course
I have 3 tables
institutes Table
id | institute_name | phone_number
----------------------------------------------
1 | vepsun | 85214542462
----------------------------------------------
2 | infocampus | 52466475544
Locations table
id | institute_id(fk) | location_name
------------------------------------------------
1 | 1 | Banglore
------------------------------------------------
2 | 1 | delhi
courses table
id | institute_id(fk) | course_name
------------------------------------------------
1 | 1 | php
------------------------------------------------
2 | 1 | delhi
I have created relations between 3 tables tables
Institute Model :
public function locations()
{
return $this->hasMany(Location::class);
}
public function courses()
{
return $this->hasMany(Course::class);
}
Course Model:
public function institute()
{
return $this->belongsTo(Institute::class);
}
Location model:
public function institute()
{
return $this->belongsTo(Institute::class);
}
So I have tried below code
public function filter(Request $request)
{
$institute = (new Institute)->newQuery();
// Search for a user based on their institute.
if ($request->has('institute_name')) {
$institute->where('institute_name', $request->input('institute_name'));
}
// Search for a user based on their course_name.
if ($request->has('course_name')) {
$institute->whereHas('courses', function ($query) use ($request) {
$query->where('courses.course_name', $request->input('course_name'));
});
}
// Search for a user based on their course_name.
if ($request->has('location_name')) {
$institute->whereHas('locations', function ($query) use ($request) {
$query->where('locations.location_name', $request->input('location_name'));
});
}
return response()->json($institute->get());
}
From above code i'm able to filter the data but it show only institution table data like below.
[
{
"id": 2,
"institute_name": "qspider",
"institute_contact_number": "9903456789",
"institute_email": "qspider#gmail.com",
"status": "1",
}
]
but what I need is when I do seach with course_name or instute_name I need to fetch data from institues table,courses and locations table. Can anyone help on this, please?
Eager load the relations in your if statements.
// Search for a user based on their course_name.
if ($request->has('course_name')) {
$institute->whereHas('courses', function ($query) use ($request) {
$query->where('courses.course_name', $request->input('course_name'));
})
->with('courses); // <<<<<< add this line
}
// Search for a user based on their course_name.
if ($request->has('location_name')) {
$institute->whereHas('locations', function ($query) use ($request) {
$query->where('locations.location_name', $request->input('location_name'));
})
->with('locations'); // <<<<<<< add this line
}
This will fetch related courses and locations as well.

Scan pivot table for two identical values

I have this pivot table:
+----+---------+-----------------+
| id | user_id | conversation_id |
+----+---------+-----------------+
| 1 | 2 | 48 |
+----+---------+-----------------+
| 2 | 1 | 48 |
+----+---------+-----------------+
I am trying to find a way to detect if two users are in the same conversation, and if they are, return the conversation. Someting like:
Conversation::with([1, 2]); // should return conversation 48
My Conversation class looks like this:
class Conversation extend Eloquent {
public function users() {
return $this->belongsToMany('User');
}
public function messages(){
return $this->hasMany('Message');
}
}
So far I have below code, but it is clumsy and very ineffective, and thought that there must exists some cleaner Laravel way:
class Conversation {
public static function user_filter(Array $users) {
$ids = array();
foreach ($users as $user)
$ids[$user->id] = $user->id;
sort($ids);
foreach (self::all() as $conversation){ // loop through ALL conversations (!!!)
$u_ids = $conversation->users->lists('id');
sort ( $u_ids );
if ($u_ids === $ids)
return $conversation;
}
return null;
}
}
Usage:
Conversation::user_filter([User::find(1), User::find(2)]); // returns conversation 48
Any ideas?
Let's first look at how you can actually retrieve the result without having too loop trough all kind of arrays...
You can use the whereHas function to filter a relationship. We can apply conditions and if they are met for the related records of a model, it will be included in the result.
$conversations = Conversation::whereHas('users', function($q){
$q->where('user_id', 1);
})->get();
So that's for one user. For two or more users it's basically the same. You just need to chain whereHass
$conversations = Conversation::whereHas('users', function($q){
$q->where('user_id', 1);
})
->whereHas('users', function($q){
$q->where('user_id', 2);
})->get();
Now this isn't very elegant. You can create a query scope to do all the work for you and keep the controller code clean. In Conversation
public function scopeWithUsers($query, array $userIds){
foreach($userIds as $id){
$query->whereHas('users', function($q) use ($id){
$q->where('user_id', $id);
});
}
return $query;
}
Usage:
$conversations = Conversation::withUsers([1,2])->get();
$conversations = Conversation::withUsers([1,2,3,4])->get();
$conversations = Conversation::withUsers([1])->get();
If it should only be one anyways use first() instead of get()
Something like this should work.
class Conversation {
public static function user_filter(Array $users) {
$ids = array();
foreach ($users as $user) {
$ids[] = $user->id;
}
return self::select("conversation_id")->whereIn("user_id", $ids)->groupBy("conversation_id")->havingRaw("count(*) = " . count($ids))->get();
}
}
Get all conversations with given users.
Conversation::whereHas('users', function($query) use ($userids) {
$query->whereIn('user_id', $userids);
})->get();
If you need get special one, you can:
Conversation::whereHas('users', function($query) use ($userids) {
$query->whereIn('user_id', $userids);
})->whereId($id)->first();

Resources