Why doesn't my Laravel conditional whereHas query work? - laravel

I am trying to create a conditional User search function. There are three filters that a user could select to search on:
user name
company name associated with user
role name associated with user
In the following example I have put all the filters in the array $searchFilters for simplicity.
I only want Laravel to search the filters that are present in the array.
The code
$q = request()->q;
$searchWildcard = '%' . $q . '%';
$searchFilters = ['name', 'roles', 'company'];
$users = User::when(in_array('roles', $searchFilters), function ($query) use ($searchWildcard) {
$query->whereHas('roles', function($query) use ($searchWildcard) {
$query->where('name', 'LIKE', $searchWildcard);
});
})->when(in_array('company', $searchFilters), function($query) use ($searchWildcard) {
$query->whereHas('company', function($query) use ($searchWildcard) {
$query->orWhere('name', 'LIKE', $searchWildcard);
});
})->when(in_array('name', $searchFilters), function($query) use ($searchWildcard) {
$query->orWhere('name', 'LIKE', $searchWildcard);
});
The first part of the query works: It retrieves the roles based on the search query by the user, but it doesn't work for the company name or user name.
Edit
It only doesn't work for the company name.

You orWhere condition is inside another where so I imagine it won't work as expected.
I would suggest changing your whereHas to orWhereHas and then changing the orWhere inside it to where:
$users = User::when(in_array('roles', $searchFilters), function ($query) use ($searchWildcard) {
$query->whereHas('roles', function ($query) use ($searchWildcard) {
$query->where('name', 'LIKE', $searchWildcard);
});
})->when(in_array('company', $searchFilters), function ($query) use ($searchWildcard) {
$query->orWhereHas('company', function ($query) use ($searchWildcard) {
$query->where('name', 'LIKE', $searchWildcard);
});
})->when(in_array('name', $searchFilters), function ($query) use ($searchWildcard) {
$query->orWhere('name', 'LIKE', $searchWildcard);
});

Related

Laravel 5.8: orWhere anulls the whereHas effect

I'm trying to do a simple search function. For that, I implemented the following query:
Profile::whereHas('accounts', function ($query) use ($id) {
$query->where('user_id', $id);
})
->where('username', 'LIKE', "%{$search}%")
->paginate(20);
The whereHas selects the profiles that the user with $id has.
Then, with the where we figure out if the search match with some username in the database.
Finally, we paginate the results.
All right so far.
The problem comes when I add a orWhere like that:
Profile::whereHas('accounts', function ($query) use ($id) {
$query->where('user_id', $id);
})
->where('username', 'LIKE', "%{$search}%")
->orWhere('fullname', 'LIKE', "%{$search}%")
->paginate(20);
I want that the query analyses if the search matchs with the username and/or the fullname. When I add the orWhere works, but the whereHas doesn't do its work and the results are from all the users.
You need to do it like this:
Profile::whereHas('accounts', function ($query) use ($id) {
$query->where('user_id', $id);
})
->where(function($query) use ($search) {
$query->where('username', 'LIKE', "%{$search}%")
->orWhere('fullname', 'LIKE', "%{$search}%");
})
->paginate(20);

Laravel search with relations

I have the following relations in my Laravel application
class Item extends Model
{
public function category()
{
return $this->belongsTo('App\Category');
}
}
and
class Category extends Model
{
public function item()
{
return $this->hasMany('App\Item');
}
}
I want to implement search functionality so I have created the following Eloquent query:
$items = Item::where('item_type', '=', 'type1')
->where(function($query) use ($q) {
$query->where('item_name','LIKE','%'.$q.'%')
->orWhere('item_description','LIKE','%'.$q.'%');
})
->paginate(10);
This is working as expected and returns the search results for 'q' based on the name and the description of the item.
As a next step, I would like to also search for the category_name. Because of the relation, I have the category_id stored in the Items table, but I would like to use the category_name in my Eloquent query.
Anyone could provide some help?
Based on feedback received, I tried:
Suggestion 1:
$items = Item::where('item_type', '=', 'type1')
->where(function($query) use ($q) {
$query
->where('item_name', 'LIKE' ,'%'.$q.'%')
->orWhere('item_description', 'LIKE' ,'%'.$q.'%');
})
->whereHas('category', function (Category $query) use ($q) {
$query->where('category_name', $q);
})
=> this gives following error message:
Argument 1 passed to
App\Http\Controllers\ItemController::App\Http\Controllers{closure}()
must be an instance of App\Http\Controllers\App\Category, instance of
Illuminate\Database\Eloquent\Builder given
Suggestion 2:
$items = Item::where('item_type', '=', 'type1')
->where(function($query) use ($q) {
$query
->where('item_name', 'LIKE' ,'%'.$q.'%')
->orWhere('item_description', 'LIKE' ,'%'.$q.'%');
})
->whereHas('category', function ($query) use ($q) {
$query->where('category_name', $q);
})
=> this does not result any search result anymore (also not for item_name and item_description).
Solution
$items = Item::where('item_type', '=', 'type1')
->where(function($query) use ($q) {
$query
->where('item_name', 'LIKE' ,'%'.$q.'%')
->orWhere('item_description', 'LIKE' ,'%'.$q.'%');
})
->orWhereHas('category', function ($query) use ($q) {
$query->where('category_name', $q);
})
->sortable(['id' => 'desc'])
->paginate(10);
As you already described relation to Category in your Item model, you have to use just whereHas method:
$items = Item::where('item_type', '=', 'type1')
->where(function($query) use ($q) {
$query->where('item_name','LIKE','%'.$q.'%')
->orWhere('item_description','LIKE','%'.$q.'%');
})
->orWhereHas('category', function ($query) use ($q) {
$query->where('category_name', 'LIKE', "%$q%");
})
->paginate(10);
You could add a whereHas and constrain it. For example:
$items = Item::where('item_type', '=', 'type1')
->where(function($query) use ($q) {
$query->where('item_name','LIKE','%'.$q.'%')
->orWhere('item_description','LIKE','%'.$q.'%');
})
->whereHas('category', function($query) {
$query->where('category_name', 'name');
})
->paginate(10);

Constrain which relationship models are returned on whereHas

An example from the documentation:
$posts = App\Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
This will look for posts that has at least one comment with foo. It will return the post model with all comments. Is there a way to restrict this to return post model and related comments that only have foo?
I'm aware we can loop through $posts with a conditional later on but I'm looking for a solution via the query builder if possible.
From the docs:
https://laravel.com/docs/5.7/eloquent-relationships#constraining-eager-loads
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
Edit, the other answer is correct.. you should add it to you actual query, not just replace it as i suggested first:
So, from your example it would look like this:
$posts = App\Post::with('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
You could also just add a lazy loading after your query
$posts->load(['comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}]);
Edit, solution suggested by #HelenChe (original asker), it's basically the same, useful if with and wherehas are going to have the same filter function.
$posts = Post::whereHas('comments', $filter = function ($query) {
$query->where('content', 'like', 'foo%');
})->with(['comments' => $filter])->get();
If you want only the posts that has at least one comment with foo with those comments. you have to combine these two function, whereHas() and with()
$posts = App\Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
->with('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();

Is there a way for Eloquent whereHas to only get() the matching results?

I have the following function
public function index(Request $request)
{
$results = Service::whereHas('conditions', function ($query) use ($request){
$query->whereIn('id', $request->conditions);
})
->whereHas('locations', function ($query) use ($request){
$ran = false;
foreach($request->locations as $location)
{
if(!$ran)
{
$query->Where('town', 'like', '%'.$location.'%')
->orWhere('city', 'like', '%'.$location.'%');
}
else
{
$query->orWhere('town', 'like', '%'.$location.'%')
->orWhere('city', 'like', '%'.$location.'%');
}
$ran = true;
}
})
->with('types', 'contacts', 'locations.datetimes')->toSql();
dd($results);
}
which produces
select * from `services` where exists
(select * from `conditions` inner join `condition_service` on `conditions`.`id` = `condition_service`.`condition_id` where `services`.`id` = `condition_service`.`service_id` and `id` in (13, 14) and `conditions`.`deleted_at` is null)
and exists
(select * from `service_locations` inner join `service_location_service` on `service_locations`.`id` = `service_location_service`.`service_location_id` where `services`.`id` = `service_location_service`.`service_id` and
(`town` like 'manchester' or `city` like 'manchester' or `town` like 'crewe' or `city` like 'crewe')
and `service_locations`.`deleted_at` is null) and `services`.`deleted_at` is null
The problem is the whereHas on locations brings back all results, whereas I only want the result where the town/city is a match. Whats the best way to achieve this?
i think you need to group your location query using the callback which wraps the query in () and just process the first location directly remove it from array and iterate over loop. so below code should work in your case.
$results = Service::whereHas('conditions', function ($query) use ($request){
$query->whereIn('id', $request->conditions);
})
->whereHas('locations', function ($query) use ($request){
$locations = $request->locations;
$query->where(function($builder) use($locations){
$builder->where('town', 'like', '%' . $locations[0] . '%')
->orWhere('city', 'like', '%'.$locations[0].'%');
unset($locations[0]); //remove first item as it is processed
foreach($locations as $location) {
$builder->orWhere('town', 'like', '%'.$location.'%')
->orWhere('city', 'like', '%'.$location.'%');
}
});
});
let me know if you find any problem with it. :)
I managed to figure it out late last night.
I moved the with() to the top of the query and made a callback on locations.
public static function newGetSearchResults($request)
{
return Service::with(['types', 'contacts', 'locations.datetimes', 'locations' => function($query) use($request) {
$ran = false;
foreach($request->locations as $location)
{
if(!$ran)
{
$query->Where('town', 'like', '%'.$location.'%')
->orWhere('city', 'like', '%'.$location.'%');
}
else
{
$query->orWhere('town', 'like', '%'.$location.'%')
->orWhere('city', 'like', '%'.$location.'%');
}
$ran = true;
}
}])
->whereHas('conditions', function ($query) use ($request){
$query->whereIn('id', $request->conditions);
})->get();
}

How to use where conditional in with Laravel?

I do request using query:
return Baber::where(function ($query) use ($request) {
// HERE CONDITION WHERE FOR USER TABLE
})->with("user")->orderBy('id', 'desc')->get();
How can I use where() inside query, that it will be comparabled with user table in with("user")?
I mean the following:
return Baber::where(function ($query) use ($request) {
$query->where('user.created_at', $request->date);
})->with("user")->orderBy('id', 'desc')->get();
It's known as constraining eager loads.
Baber::with(['user' => function ($query) use ($request) {
$query->where('user.created_at', '=', $request->date);
}])
->orderBy('id', 'desc')
->get();

Resources