Laravel multiple where condition in whereHas callback - php

I am creating a general search function with Laravel 5.2 and I want to display all the books in which occurs the searched keyword in: the book's title, book's subject, book's plot, book's author name, book's author surname;
I thought that this code would work:
$results = Book::whereHas('author', function ($query) use ($keyword)
{
$query->where('surname', 'LIKE', '%'.$keyword.'%')
->orWhere('name', 'LIKE', '%'.$keyword.'%');
})
->orWhere('title', 'LIKE', '%'.$keyword.'%')
->orWhere('plot', 'LIKE', '%'.$keyword.'%')
->orWhere('subject', 'LIKE', '%'.$keyword.'%')
->get();
But when I use as a keyword the name of an author, I get as a results the whole library.
Instead if I enter the surname of an author it works perfectly.
I found out this solution, that is not optimal in my opinion, but at least it works:
$results = Book::whereHas('author', function ($query) use ($keyword)
{
$query->where('surname', 'LIKE', '%'.$keyword.'%');
})
->orWhereHas('author', function ($query) use ($keyword)
{
$query->where('name', 'LIKE', '%'.$keyword.'%');
})
->orWhere('title', 'LIKE', '%'.$keyword.'%')
->orWhere('plot', 'LIKE', '%'.$keyword.'%')
->orWhere('subject', 'LIKE', '%'.$keyword.'%')
->get();
Any suggestions?
Thank you in advance for your help!

The issue is that the where clauses added in your closure aren't the only where clauses being applied to the subquery. The whereHas() method generates a subquery that starts with a where clause on the ids for the relationship. Because of this, your subquery isn't just where x or y, it is actually where x and y or z.
Given this set of where clauses, and the order of operations for logical operators, if the z condition is true (your 'name' condition), the whole where clause will return true, meaning the constraint on only looking at related objects is completely ignored. Since the constraint on related objects is ignored, the has condition will be true for every record (if 'name' matches any record).
Below is an example of your logical conditions:
// first boolean is the related keys check
// second boolean is the surname check
// third boolean is the name check
// this is your current logic
// as you can see, this returns true even when looking at an
// author not even related to the book.
var_export(false && false || true); // true
// this is what your logic needs to be
var_export(false && (false || true)); // false
So, to solve this issue, you need to wrap your or conditions in parentheses, so they're evaluated as you intended. You can do this by passing a closure to the where() method, and then any conditions added inside the closure will be inside parentheses:
$results = Book::whereHas('author', function ($query) use ($keyword) {
$query->where(function ($q) use ($keyword) {
$q->where('surname', 'LIKE', '%'.$keyword.'%')
->orWhere('name', 'LIKE', '%'.$keyword.'%');
});
})
->orWhere('title', 'LIKE', '%'.$keyword.'%')
->orWhere('plot', 'LIKE', '%'.$keyword.'%')
->orWhere('subject', 'LIKE', '%'.$keyword.'%')
->get();

have you tried creating a local query scope for your author/user model such as named() then applying that
public function scopeNamed($query)
{
return $query->where('surname', 'LIKE', '%'.$keyword.'%')
->orWhere('name', 'LIKE', '%'.$keyword.'%');
}
Then your query
$results = Book::whereHas('author', function ($query) use ($keyword)
{
$query->named();
})
->orWhere('title', 'LIKE', '%'.$keyword.'%')
->orWhere('plot', 'LIKE', '%'.$keyword.'%')
->orWhere('subject', 'LIKE', '%'.$keyword.'%')
->get();
https://laravel.com/docs/master/eloquent#query-scopes

Related

Laravel With() along side with Where()

Take as example the following eloquent code:
Customers::with('ShippingMode')
->with('PaymentMethod')
->with('BarCodes')
->where(function($query) use ($find)
{
$query->where('name', 'like', "%$find%")
->orWhere('vat_number', 'like', "%$find%");
});
Now I would like to add more orWhere conditions but looking into PaymentMethod table, like such:
Customers::with('ShippingMode')
->with('PaymentMethod')
->with('BarCodes')
->where(function($query) use ($find)
{
$query->where('name', 'like', "%$find%")
->orWhere('vat_number', 'like', "%$find%")
->orWhere('PaymentMethod.description', 'like', "%$find%");
});
But this does not work as it returns the error:
Column not found: 1054 Unknown column 'PaymentMethod.descricao' in
'where clause'
I know that if I use the old joins it works great:
Customers::select('customer.*')
->from('customers AS customer')
->leftJoin('payment_methods AS payment_method', 'payment_method.id', '=', 'customer.payment_method_id')
->where(function($query) use ($find)
{
$query->where('name', 'like', "%$find%")
->orWhere('vat_number', 'like', '%$find%')
->orWhere('payment_method.description', 'like', '%$find%');
});
PLEASE NOTE: The variable $find can be empty and when it's empty it returns all the results without filter applied, which means I cannot use the whereHas function.
orWhereHas allow you to add customized constraints to a relationship constraint:
Customers::with('ShippingMode')
->with('PaymentMethod')
->with('BarCodes')
->where(function($query) use ($find) {
$query->where('name', 'like', "%$find%")
->orWhere('vat_number', 'like', "%$find%");
})->orWhereHas('PaymentMethod', function($query) use ($find) {
$query->where('description', 'like', "%$find%");
});
Something like this should work:
use Illuminate\Database\Eloquent\Builder;
Customers::with(['ShippingMode', 'PaymentMethod', 'BarCodes'])
->when($find, function (Builder $query, $find) {
$query->where(function (Builder $query) use ($find) {
$query->where('name', 'like', "%{$find}%")
->orWhere('vat_number', 'like', "%{$find}%")
->orWhereHas('PaymentMethod', function (Builder $query) use ($find) {
$query->where('description', 'like', "%{$find}%");
});
});
})->get();
With when() it will only apply the filter when $find has a value and it allows you to search on the Customer columns with the where() / orWhere() and the PaymentMethod relationship with orWhereHas().
You can read more about when() and whereHas() here:
when
whereHas

I want to get all images which either have a word in their name, as one of their tags or as category

I'm trying to add search functionality to my website and have been tinkering with the eloquent queries I'd need to execute. Currently I've made 3 queries and each gets images meeting a certain criteria, however, I'm not sure how to combine it into 1 query that would spit all images that meet one, two or all criteria.
$images = Image::where('name', 'like', '%'.$query.'%')->get();
This query gets all images that have a name similar to the searched word.
$images = Image::whereHas('tags', function($q) use ($query) {
return $q->where('name', 'like', '%'.$query.'%');
})->orderBy('created_at', 'desc')->get();
This query gets all images that have a tag similar to the searched word.
$images = Image::whereHas('category', function($q) use ($query) {
return $q->where('name', 'like', '%'.$query.'%');
})->orderBy('created_at', 'desc')->get();
And finally, this query gets all images that have a category similar to the searched word.
public function search($query){
$images = Image::where('name', 'like', '%'.$query.'%')->get();
$images = Image::whereHas('tags', function($q) use ($query) {
return $q->where('name', 'like', '%'.$query.'%');
})->orderBy('created_at', 'desc')->get();
$images = Image::whereHas('category', function($q) use ($query) {
return $q->where('name', 'like', '%'.$query.'%');
})->orderBy('created_at', 'desc')->get();
return view('search', ['images' => $images]);
}
Is this the correct way to create a search functionality? Is there anything I could do to enhance it further? Is there some obvious problem that I personally might be overseeing? I'd appreciate any tips and tricks since I believe the search functionality is important for CRUD applications like mine.
Merge the constraint into a single query:
$images = Image::where('name', 'like', '%'.$query.'%')
->orWhereHas('tags', function($q) use ($query) {
return $q->where('name', 'like', '%'.$query.'%');
})->orWhereHas('category', function($q) use ($query) {
return $q->where('name', 'like', '%'.$query.'%');
})->latest()
->get();
latest() is equivalent to orderBy('created_at', 'desc').

How to return query from variable?

I try to search first_name, last_name and city but city is now always set whether the users want to set it with city or not. so I make query like this if request has city then filter it
$users = \App\User::join('profiles', 'users.id', '=', 'profiles.member_id')
->where(function($query) {
$query->where('first_name', 'like', '%z%')
->orWhere('last_name', 'like', '%z%');
})
->paginate(10);
if ($request->has('city')) {
$users->where('location', 'like', '%bandung%');
}
but where location like its like never used even request has city. i've try var_dump $users and count it and end up with no city query
Do little change in sequence of your lines of code:
$users = \App\User::join('profiles', 'users.id', '=', 'profiles.member_id')
->where(function($query) {
$query->where('first_name', 'like', '%z%')
->orWhere('last_name', 'like', '%z%');
});
if ($request->has('city')) {
$users->where('location', 'like', '%bandung%');
}
$users_data = $users->paginate(10);
The variable $users_data will be having proper result.

how to set count(*) as field name in laravel

I am using larvel eloquent.
i am using this query using model. My code is
$books = Book::select('title', 'author', Book::raw('count(*) as copies'))
->where('title', 'like', '%'.$name.'%')
->orWhere(function ($query) use ($name) {
$query->where('author', 'like', '%'.$name.'%')
->where('subject', 'like', '%'.$name.'%');
})
->groupBy('title','author')
->get();
I got the error
"strtolower() expects parameter 1 to be string, object given"
I know the error is in count(). The code
raw('count() as copies') is used in table.
But I dont know how to use count(*) as copies in model eloquent. My model name is Book.
One more doubt i have, can we use multple fields in groupBy, ie
groupBy('title','author')
use selectRaw()
$books = Book::select('title', 'author'))
->where('title', 'like', '%'.$name.'%')
->orWhere(function ($query) use ($name) {
$query->where('author', 'like', '%'.$name.'%')
->where('subject', 'like', '%'.$name.'%');
})
->selectRaw('count(*) as copies')
->groupBy('title','author')
->get();
Try this, SelectRaw
$books = Book::select('title', 'author')
->selectRaw('count(*) as copies')
->where('title', 'like', '%'.$name.'%')
->orWhere(function ($query) use ($name) {
$query->where('author', 'like', '%'.$name.'%')
->where('subject', 'like', '%'.$name.'%');
})
->groupBy('title','author')
->get();
You have this posiblity also
->select('title','author', DB::raw('count(*) as copies'))
Laravel Documentation Raw

Query for multiple fields with Laravel

So I am trying to create a search functionality so that you can query for both first and last name. What can I use as an AND statement for the query?
public function find()
{
$search = Input::get('contact_search');
$query = Contact::orderBy('name', 'desc');
if (is_null($search))
{
$contacts = $query->paginate(15);
//return View::make('contacts.index')->with(array('contacts' => $contacts));
} else {
$contacts = $query->where('firstName', 'LIKE', "%{$search}%")
->orWhere('lastName', 'LIKE', "%{$search}%")
->paginate(15);
$contact = Contact::find(1);
}
return View::make('hello')->with(array('contacts' => $contacts));
}
I have tried
$query->where('firstName', 'LIKE', "%{$search}%")
->Where('lastName', 'LIKE', "%{$search}%")
but that does not work either. Any advice would be awesome! Thanks.
where() acts as an and statement already. Just chain them together.
$people = People::where('first_name','=','John')->where('last_name','=','Doe')->get();
In your query, you have ->Where. Make sure its lowercase.
Also, your method could use some optimization. You are searching for multiple contacts and paginating the results, but then you are doing a find(1) for some reason. A better approach is to just do the following:
$contact = Contact::orderBy('name','desc')
->where('firstName', 'LIKE', "%{$search}%")
->where('lastName', 'LIKE', "%{$search}%")
->first();
That will return your first contact in the results. No need for pagination. And find() actually searches for records based off of id's anyways so you don't want to use that in this case.
If you use where it will add AND if you use multiple where. However I don't know if in your case you want to use AND because you if someone will put into form Jo you will search for poeple that hat Jo both in name and surname, so for example John Smith won't be found here because his surname doesn't contain Jo.
So answering your question you could use:
$contacts = $query->where('firstName', 'LIKE', "%{$search}%")
->where('lastName', 'LIKE', "%{$search}%")
->paginate(15);
but probably this won't make much sense.
It's hard to also say what exactly you do here, because you have here:
$contacts = $query->where('firstName', 'LIKE', "%{$search}%")
->orWhere('lastName', 'LIKE', "%{$search}%")
->paginate(15);
$contact = Contact::find(1);
using those 2 lines first you look for people who have $search in first or last name and paginate them, and when using Contact::find(1); you find person with id 1. It also doesn't seem to be good solution to anything here.
If you would like to find the first record that have $search either in first or last name, you should use:
$contacts = $query->where('firstName', 'LIKE', "%{$search}%")
->where('lastName', 'LIKE', "%{$search}%")
->first();
without orWhere and without paginate.

Categories