简体   繁体   中英

Laravel eloquent: How to filter with where() and whereHas()

I am trying to filter my users based on categories in which they have posted something and also the users directly by search string:

$users = User::when($search, function ($query) use ($request) {
    $query->where('name', 'like', '%' . $request['search'] . '%')
          ->orWhere('email', 'like', '%' . $request['search'] . '%');

    return $query;
})
->when($categories, function ($query) use ($request) {
    return $query->whereHas('posts.category', function ($query) use ($request) {
        $query->whereIn('name', $request['categories']);
    });
})
->select('id', 'name', 'email')
->paginate(10);

When I filter for categories only (without $search ) it is working as expected but as soon as I type a search string the result is filtered by the search string $request['search'] but ignores the category filtering completely.

So for example:

The user named "Jon" has a post in category "Lifestyle".

Another user named "Jonn" has a post in category "Sports".

Now if I filter for category only "Sports" I get the correct result -> "Jonn".

But if I filter additionally with the search string "Jon" the result is -> "Jon" and "Jonn" which is not the expected result because it should filter for "Sports" and name "%Jon%" so again the result should be only "Jonn".

I don't know how I can achieve that filter combination of where() and whereHas() .

Your issue is nesting of the constraints. Translated to SQL, your query looks like this:

SELECT id, name, email
FROM users
WHERE name like '%foo%'
   OR address like '%foo%'
   OR email like '%foo%'
  AND EXISTS (
    SELECT *
    FROM categories c
      INNER JOIN posts p ON p.category_id = c.id
    WHERE c.name IN ('bar', 'baz')
  )

Note: pagination omitted

As you can see, it isn't quite clear what the WHERE statements are supposed to do. But that can be fixed rather easily by wrapping all of the search constraints in an extra where() :

$users = User::when($search, function ($query) use ($request) {
    $query->where(function $query) use ($request) {
        $query->where('name', 'like', '%' . $request['search'] . '%')
            ->orWhere('address', 'like', '%' . $request['search'] . '%')
            ->orWhere('email', 'like', '%' . $request['search'] . '%');
    });
})
->when($categories, function ($query) use ($request) {
    $query->whereHas('posts.category', function ($query) use ($request) {
        $query->whereIn('name', $request['categories']);
    });
})
->select('id', 'name', 'email')
->paginate(10);

This way your query will look like this:

SELECT id, name, email
FROM users
WHERE
  (
     name like '%foo%'
     OR address like '%foo%'
     OR email like '%foo%'
  )
  AND EXISTS (
    SELECT *
    FROM categories c
      INNER JOIN posts p ON p.category_id = c.id
    WHERE c.name IN ('bar', 'baz')
  )

A small note on the side: You don't have to return the $query from the callbacks.

You can try like this:

$users = User::where(function ($query) {
    $query->when($search, function ($query) use ($request) {
        $query->where('name', 'like', '%' . $request['search'] . '%')
          ->orWhere('address', 'like', '%' . $request['search'] . '%')
          ->orWhere('email', 'like', '%' . $request['search'] . '%');
        })
    $query->when($categories, function ($query) use ($request) {
           $query->whereHas('posts.category', function ($query) use ($request) {
           $query->whereIn('name', $request['categories']);
        });
    })
})
->select('id', 'name', 'email')
->paginate(10);

I think you don't need your return. But you can achieve what do you want if you put your when statements in one where function like this.

I didn't test this solution so if you have errors provide it in comment.

Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM