I'm building up a filterable list with Laravel 5.0 and I'm crashing on following Problem.
I get the filter parameters from a HTML form and pass them to the Query Builder, but if a form input stays empty all rows of the table filtered by the other filters should be returned.
Example code:
$collection = Model::withTrashed()
->where('attr1', 'LIKE', \Request::input('attr1', '%'))
->where('attr2', 'LIKE', \Request::input('attr2', '%'))
->where('attr3', 'LIKE', \Request::input('attr3', '%'))
->get();
This seems to be most correct code for me, but it doesn't work as expected. Do you know a good solution for my Problem? I don't want to integrate a messy switch/case statement for proofing for existence and building the collection up manually. :(
Thanks for your help.
It seems that your problem is that you are always passing the filters to the query and you should only pass them if they are not empty.
Maybe somethig like this would work for you:
$results = Model::withTrashed();
if (\Request::input('attr1')) {
$results->where('attr1', 'LIKE', \Request::input('attr1', '%'));
}
if (\Request::input('attr2')) {
$results->where('attr2', 'LIKE', \Request::input('attr2', '%'))
}
if (\Request::input('attr3')) {
$results->where('attr3', 'LIKE', \Request::input('attr3', '%'))
}
$collection = $results->get();
You need to tweak your query a bit like this:
$collection = Model::withTrashed()
->where('attr1', 'LIKE', '%' . \Request::input('attr1') . '%')
->where('attr2', 'LIKE', '%' . \Request::input('attr2') . '%')
->where('attr3', 'LIKE', '%' . \Request::input('attr3') . '%')
->get();
This will make sure that the LIKE clause is still correct when an empty input parameter is sent.
Related
I am using laravel spatie for filtering the model i have following filters in allowed filter array
$this->allowedFilters = [
'name',
AllowedFilter::exact('company_id'),
'location',
'summary',
'client_name',
];
when I make request like
past-projects?filter[company_id]=${companyId}&filter[name]=${search}&filter[summary]={company_id}
it searches for the data which exactly contains all the passed search params with AND condition and returns no results if any of passed param do not match. I want to get results if any of the passed param match.
Any help will be appreciated.
As per Spatie Laravel Query Builder documentation the allowedFilters() method will take the array of strings and return a result where all the parameters match.
The bad news: Therefore, it will not work as how you are intending to use it.
What you want is Laravel's Or Where Clause.
The good news: since Spatie Laravel Query Builder extends Laravel's default Eloquent query builder you can use both Laravel queries and Spatie queries in conjunction.
Say you want to get all users from "Org ABC" and from that result you want to get all users with either the name "Jack" or the location "USA" or the client_name "John" or the summary "Hello World". Then you can try
$result = QueryBuilder::for(Projects::class)
->allowedFilters(['company_id']
->where(function ($query) use ($name, $location, ...)
$query
->where('projects.user.name', 'like', '%$title%')
->orWhere('projects.location', 'like', '%$location%')
->orWhere() ....
);
return $result
I came across this issue... Here's a way I resolved it.
You could also use the callback in the query builder like this...
$users = QueryBuilder::for(User::class)
->allowedFilters([
'user_type',
'user_city',
AllowedFilter::exact('status')->default('new'),
AllowedFilter::callback('search', function($query, $value){
$query->where('name', 'LIKE', '%' . $value . '%')
->orWhere('bio', 'LIKE', '%' . $value . '%')
->orWhere('state', 'LIKE', '%' . $value . '%')
->orWhere('address', 'LIKE', '%' . $value . '%');
})
])->get();
then the URL will look like this for example
?filter[user_type]=customer&filter[search]=doe&filter[user_city]=boston
The search parameter will be used to check the name, bio, state and address for any match.
I am trying to search relevant jobs from a list on jobs on my DB according to what user searches for and if the search term matches the job title its highly relevant search result so I am trying to assign the relevancy value to job objects right after their where condition gets satisfied. Also cleaner way of writing orWhere conditions?
public function search()
{
$searchText = $_GET['searchText'];
$jobs = Jobs::where('name', 'LIKE', '%' . $searchText . '%') //how to do $jobs->relevancy = 100 here? if this where condition is satisfied?
->orwhere('skills', 'LIKE', '%' . $searchText . '%')
->orwhere('desc', 'LIKE', '%' . $searchText . '%') //$jobs->relevancy = 10 here
->orwhere('desc1', 'LIKE', '%' . $searchText . '%')
->orwhere('desc2', 'LIKE', '%' . $searchText . '%')
->orwhere('desc3', 'LIKE', '%' . $searchText . '%')
->orwhere('desc4', 'LIKE', '%' . $searchText . '%')
->get();
return view('results', compact('jobs'));
}
If you want to group orWhere conditions to then apply another WHERE (AND) statement, you can use a closure for the grouping.
Imagine that you want something like this:
WHERE A
AND (WHERE B OR WHERE C)
AND (WHERE D OR WHERE E)
Then you could do:
$results = Model::where('A', 'something')
->where(function ($query) {
$query->where('B', 'something')
->orWhere('C', 'something');
})
->where(function ($query) {
$query->where('D', 'something')
->orWhere('E', 'something');
})
->get();
There's no easy way to know which part of the where clauses actually matched the query. You'd need to re-do the text comparisons after you got the results, which is probably not ideal.
In these sort of situations you might want to consider a full text search.
If you're using MySQL you can create a FULLTEXT index and then use MATCH ... AGAINST. (other DBMS have a different syntax but generally there is support).
$searchText = request()->searchText;
$jobs = Jobs::whereRaw('MATCH(name) AGAINST (? IN NATUAL LANGUAGE MODE)', [$searchText])
->orWhereRaw('MATCH(desc) AGAINST (? IN NATUAL LANGUAGE MODE)', [$searchText])
// ...
selectRaw('(100*MATCH(name) AGAINST (? IN NATUAL LANGUAGE MODE) + 10*MATCH(name) AGAINST (? IN NATUAL LANGUAGE MODE)) as score', [$searchText,searchText])
->addSelect("*")
->orderBy('score', 'desc')
->get();
MATCH ... AGAINST will return a relevance score so by doing e.g. 100*MATCH(name).. you are adding a weight of 100 to that match. The overall result should be ordered based on the total relevance of the search.
More on full text search in MySQL in the manual
If you are using this kind of condition in many places then you can do the following!
1- In your AppServiceProvider inside boot methode
use Illuminate\Database\Query\Builder;
public function boot()
{
Builder::macro('search', function($field, $string){
return $string ? $this->where($field, 'like', '%'.$string.'%') : $this;
});
Builder::macro('orSearch', function($field, $string){
return $string ? $this->orWhere($field, 'like', '%'.$string.'%') : $this;
});
}
2- Do your Query like the following
$jobs = Jobs::where(function($query) {
if($searchText != "") {
$query->search('skills', $searchText);
$query->orSearch('desc', $searchText);
$query->orSearch('desc1', $searchText);
$query->orSearch('desc2',$searchText);
$query->orSearch('desc3',$searchText);
}
})->get();
I think this is a cleaner way of doing this kind of query.
I have a Laravel Package where I filter Users that are fetched from the database with a User Eloquent Model. So I have something like
if ($request->get('search')) {
User::where('email', 'like', '%' . $search . '%')
->orWhere('first_name', 'like', '%' . $search . '%')
->orWhere('name', 'like', '%' . $search . '%')
...
Now I want to add ore preprocess some filters on the Users before this is done from the project itself. What first came to my mind is to fire an event like
event(new UserFilterStart($users));
where I modify Users before i run the filters. So the code would look like this:
$users = User::all();
event(new UserFilterStart($users));
if ($request->get('search')) {
$users->where('email', 'like', '%' . $search . '%')
->orWhere('first_name', 'like', '%' . $search . '%')
->orWhere('name', 'like', '%' . $search . '%')
I did not manage to hand over the variable by reference, so i can manipulate it. Which is probably a good thing, because then this might have been my final solution.
I get that it is not the way Events should be used and I am looking for alternatives. How can I achieve to inject/modify the code from the package? Considering that I own this package.
Ok, it turned out that it just works fine with the Event. I just made a mistake somewhere else. Are there any real shortcomings if I use Events like that?
I'm trying to ignore specific types of objects in the query. there are types where I have banned or deleted data which I don't want the user be able to find. my query looks something like :
$posts = DB::table('posts')->where('content', 'like', '%' . $request['content'] . '%')
->where('isfutured', '!=', '0')
->orderByDesc('id')->get();
But now I also want to not get the data with isfutured of 4. how do I do that? or can I somehow prevent these in the Post model and do my regular Eloquent query?
using query builder
for multiple condition used whereIn or whereNotIn
$isfutured = [0,4]; // add in array which did not want to get
$posts = DB::table('posts')->where('content', 'like', '%' . $request['content'] . '%')
->whereNotIn('isfutured', $isfutured)
->orderByDesc('id')->get();
using elequent
$posts = Post::whereNotIn('isfutured', $isfutured)->where('content', 'like', '%' . $request['content'] . '%')->orderByDesc('id')->get();
What I'm trying to do is setup a server side configuration for a table data. So I have a model CounterLog that has 3 relationships set [belongsTo] category, location, user. I want a query to filter all CounterLog data including relationships, with offset, limit and orderBy methods set and in the same time retrieve all the filtered rows ignoring offset and limit. Here is what I managed until now and maybe understand better what I want:
$search_query = function($q) use ($search) {
$q->where('name', 'like', '%' . $search . '%');
};
$query = CounterLog::where('created_at', 'like', '%' . $search . '%')
->orWhereHas('category', $search_query)
->orWhereHas('location', $search_query)
->orWhereHas('user', $search_query);
$logs = $query->offset($offset)->limit($limit)->get();
$logs_total = $query->offset(0)->count();
In the last line I'm using $query->offset(0) because for some reason if offset is set to a number $logs_total becomes 0. I'm not sure this is the proper way to do it.. but even like this I have no idea how to use orderBy for ex. category.name.
I know I can always use raw queries in eloquent but I want to know if there is a way to use ORM and relationships. I would really appreciate if you could help me with this..cuz the struggle is real.
Thanks a lot :)
Apparently I haven't got a solution with ORM so I did it with "raw" queries:
$query = $this->db->table('counter_logs')
->leftJoin('categories', 'counter_logs.category_id', '=', 'categories.id')
->leftJoin('locations', 'counter_logs.location_id', '=', 'locations.id')
->leftJoin('users', 'counter_logs.user_id', '=', 'users.id');
->where('counter_logs.created_at', 'like', '%' . $search . '%')
->orWhere('categories.name', 'like', '%' . $search . '%')
->orWhere('locations.name', 'like', '%' . $search . '%')
->orWhere('users.name', 'like', '%' . $search . '%');
->select('counter_logs.id as id', 'categories.name as category', 'locations.name as location', 'users.name as user', 'counter_logs.created_at as date');
$json['total'] = $query->count();
$logs = $query->offset($offset)->limit($limit)->orderBy($sort, $order)->get();
Try to swap statements:
$logs_total = $query->count();
$logs = $query->offset($offset)->limit($limit)->get();
Or clone base query, like this:
$total_count_query = clone $query;
$logs = $query->offset($offset)->limit($limit)->get();
$logs_total = $total_count_query->count();