I have this function in my controller:
if ($request->get('query')) {
$query = $request->get('query');
$data = DB::table('users')
->where('is_archive', '=', 0)
->where(function($string) use ($query) {
$string->where('first_name', 'LIKE', '%' . $query . '%')
->orWhere('last_name', 'LIKE', '%' . $query . '%')
->orWhere('church_id', 'LIKE', '%' . $query . '%');
})
->get();
}
What it does is a real-time search result that is listing same exact results as you type a letter. Same as the search bar of google that every time when you type a letter. it will show results that is the same as the currently typed text. I wish to put security features that will prevent the rendering of <, >, ', " etc. but it will accept space (spacebar) character. What I have done so far is:
$sanitizedQuery = strtolower(trim(preg_replace('/\s+/', '', $query)));
but I seem to fail to achieve my desired outcome. Any suggestion is highly appreciated.
Related
I'm trying to make a search feature on laravel. now I'm using this query:
$products = Product::with(['category', 'store'])
->when($keywords, function ($query) use ($keywords) {
$query->where('name', 'LIKE', '%' . $keywords . "%")
->orWhere('description', 'LIKE', '%' . $keywords . '%');
})->get()
The problem is, say on the database i've a product with the name corrupti, If i searched by the exact name or removed a character from beginning or end it works fine, but if I changed a single character it returns an empty list.
Users are likely to make typos, so I want to be able to find the product is the user typed corrupta, corrupi instead of corrupti.
I know this is not a simple task to achive, I googled many things but I didn't find a solution.
One thing I've came accross is the php similar_text funciton, it may be useful but I didn't find a way to include it in the database query.
https://www.php.net/manual/en/function.soundex.php
https://www.php.net/manual/en/function.metaphone.php
use metaphone (or soundex) to encode words you want to be searchable
put them in a database column say products.name_as_metaphone
make your search function encode searched word to metaphone, then make it look in the metaphone column (and not in product.name)...
profit
try this:
$query = Product::query();
$searches = explode(' ', $request->query('q'));
if (isset($searches)) {
foreach ($searches as $search) {
$query->where(function ($q) use ($search) {
$q->where('code', 'ilike', '%' . $search . '%')
->orWhere('name', 'ilike', '%' . $search . '%');
});
}
}
$data = $query->get();
return ([
'success' => true,
'response' => $data,
'message' => 'Your message here'
]);
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 have a query like so
$data = City::with('hotel')->orwherehas('hotel', function ($query) use ($user_input) {
//here i want to limit this result to 5
$query->where('name', 'LIKE', '%' . $user_input . '%')->take(5);
// $query->take(5); i have tried this too
})->orWhere('name', 'LIKE', '%' . $user_input . '%')->get();
inside the whereHas clause, I have a query that I want to limit to 5, now I tried limit, take but no luck after that where nothing is working I don't know why
You can pass your query to the ->with() query builder method:
$data = City::with(['hotel' => function($query) use ($user_input) {
$query->where('name', 'LIKE', '%' . $user_input . '%')->limit(5);
}])
->where('name', 'LIKE', '%' . $user_input . '%')
->get();
This will get all hotels associated with a city which have the user input, where the city contains the user input.
Note that the ->orWhere() is not used here.
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();