Laravel雄辩:如何使用where()和whereHas()进行过滤

时间:2019-08-16 07:57:36

标签: php laravel eloquent

我正在尝试根据用户发布过文章的类别来过滤用户,并根据搜索字符串直接过滤用户:

$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);

仅过滤类别(不使用$search时,它可以按预期工作,但是当我键入搜索字符串时,结果将被搜索字符串$request['search']过滤,但完全忽略了类别过滤

例如:

名为“乔恩”的用户在“生活方式”类别中有一条帖子。

另一个名为“ Jonn”的用户在“体育”类别中有一条帖子。

现在,如果我仅针对“体育”类别进行过滤,则会得到正确的结果->“乔恩”。

但是,如果我另外使用搜索字符串“ Jon”进行过滤,结果将是->“ Jon”和“ Jonn”,这不是预期的结果,因为它应该过滤“ Sports”并命名为“%Jon%”结果应仅为“ Jonn”。

我不知道如何实现where()whereHas()的过滤器组合。

2 个答案:

答案 0 :(得分:4)

您的问题是约束的嵌套。转换为SQL后,您的查询如下所示:

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')
  )

注意:分页省略

如您所见,尚不清楚WHERE语句应该做什么。但这可以通过将所有搜索约束包装在一个额外的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);

这样,您的查询将如下所示:

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')
  )

侧面有一个小提示:您不必从回调中返回$query

答案 1 :(得分:1)

您可以尝试这样:

$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);

我认为您不需要退货。但是,如果将when语句放在这样的一个where函数中,则可以实现您想要的东西。

我没有测试此解决方案,因此如果您有任何错误,请在评论中提供。

祝你好运!