Laravel 的 where or 查询

我使用 Laravel 开发已经半年多,两个项目。Laravel 完善的文档,以及强大的社区,几乎可以应付所有疑问,这使我很少去看低层代码实现逻辑。尽管能很好的实现逻辑,但是对代码的把控还不是很好,就在前几天,我需要写一个带有 OR 条件的查询,当时让我煞费苦心,我搜索了很多信息,都没有查到,所有关于稍微复杂一点的 OR 查询都是在讲解这个匿名函数实现:


  DB::table('users')
   ->where('name', '=', 'John')
   ->where(function ($query) {
       $query->where('votes', '>', 100)
             ->orWhere('title', '=', 'Admin');
   })
   ->get();

而这并不能满足我,也许是我的关键词不对,没有找到那个完美的答案,并且我想要更多更灵活的方式。

我要实现的 SQL 大概是这样的:


 SELECT * FROM user
  WHERE 
    group_id = 'group id'
   AND (
     name = 'name'
     OR mobile_number = 'mobile number'
     OR email = 'email'
     OR `score` > 1000
   ) 

这是一类很常见的业务逻辑,可能你会觉得很简单,我也知道怎么去实现:


  DB::table('users')
     ->where('group_id', 'group id')
     ->where(function ($query) {
         $query->where('name', 'name')
               ->orWhere('mobile_number', '=', 'mobile number')
               ->orWhere('email', '=', 'email')
               ->orWhere('score', '>', '1000');
     })
     ->get();

但是实际的逻辑并不是这样的,我还需要去判断是否有对应的参数,才需要把对应查询条件写入,就像这样:


  DB::table('users')
     ->where('group_id', 'group id')
     ->where(function ($query) {
        if ($params['name']) {
          $query->orWhere('name', $params['name'])
        }

        if ($params['mobile_number']) {
          $query->orWhere('mobile_number', $params['mobile_number'])
        }

        if ($params['email']) {
          $query->orWhere('email', $params['email'])
        }

        if ($params['score']) {
          $query->orWhere('score', '>', $params['score'])
        }

     })
     ->get();

我知道这可行,一直都是这样写的,但我觉得强大的 Laravel 肯定不会仅仅提供这种方式,于是我决定看一眼低层代码,很幸运,我有新的发现:


/**
 * Add a basic where clause to the query.
 *
 * @param  \Closure|string|array  $column
 * @param  mixed   $operator
 * @param  mixed   $value
 * @param  string  $boolean
 * @return $this
 */
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
    // If the column is an array, we will assume it is an array of key-value pairs
    // and can add them each as a where clause. We will maintain the boolean we
    // received when the method was called and pass it into the nested where.
    if (is_array($column)) {
        return $this->addArrayOfWheres($column, $boolean);
    }

    // Rest of code ...
}

/**
 * Add an array of where clauses to the query.
 *
 * @param  array  $column
 * @param  string  $boolean
 * @param  string  $method
 * @return $this
 */
protected function addArrayOfWheres($column, $boolean, $method = 'where')
{
    return $this->whereNested(function ($query) use ($column, $method, $boolean) {
        foreach ($column as $key => $value) {
            if (is_numeric($key) && is_array($value)) {
                $query->{$method}(...array_values($value));
            } else {
                $query->$method($key, '=', $value, $boolean);
            }
        }
    }, $boolean);
}

where 方法中,我只保留了需要重点关注的一段代码,如果条件满足,将会进入 addArrayOfWheres 方法,在这里解析以数组方式传递进来的条件参数,并且该组条件会被分组,也就是会用 () 包起来。有两种方式可以让查询条件分组,一是数组传参,二是匿名函数。类似我这种 OR 条件的查询,关键的就是让查询正确分组。

另外一个关键代码:


if (is_numeric($key) && is_array($value)) {
    $query->{$method}(...array_values($value));
}

数组参数会被展开,看到这个,我想我的代码可以写成这样:


  $orWhere = [];
  if ($params['name']) {
      $orWhere[] = ['name', '=', $params['name'], 'OR'];
  }
  if ($params['mobile_number']) {
      $orWhere[] = ['mobile_number', '=', $params['mobile_number'], 'OR'];
  }
  if ($params['email']) {
      $orWhere[] = ['email', '=', $params['email'], 'OR'];
  }
  if ($params['score']) {
      $orWhere[] = ['score', '>', $params['score'], 'OR'];
  }

  DB::table('users')
     ->where('group_id', 'group id')
     ->where($orWhere)
     ->get();

$orWhere 会被分组且被 ...array_values($value) 展开。

也可以这样:


  $orWhere = [];
  if ($params['name']) {
      $orWhere['name'] = $params['name'];
  }
  if ($params['mobile_number']) {
     $orWhere['mobile_number'] = $params['mobile_number'];
  }
  if ($params['email']) {
      $orWhere['email'] = $params['email'];
  }
  if ($params['score']) {
      $orWhere[] = ['score', '>', 1000, 'OR'];
  }

  DB::table('users')
     ->where('group_id', 'group id')
     ->where(function ($query) use ($orWhere) {
        $query->orWhere($orWhere);
     })
     ->get();

最终的 sql 是这样的

select
  *
from
  `users`
where
  `group_id` = 'group id'
  and (
    (
      `name` = 'name'
      or `mobile_number` = 'mobile number'
      or `email` = 'email'
      OR `score` > 1000
    )
  )

虽然很多人都知道这个,我还是希望这能带来些许启发。

那么还有没有更多更灵活的方式呢?

本作品采用《CC 协议》,转载必须注明作者和本文链接
? 我的导航网站已经可以公开使用啦:Cootab
本帖由系统于 2年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 8
DB::table('users')
     ->where('group_id', 'group id')
     ->when($params['name'], function($query) use ($params['name']) {
         $query->orWhere('name', $params['name']);
     });

这个写法也可以吧

4年前 评论
chuoke (楼主) 4年前
qf-Z (作者) 4年前
 DB::table('users')
     ->where('group_id', 'group id')
     ->where(function ($query) use ($orWhere) {
        $query->orWhere($orWhere);
     })->get();

我是用这种方式处理的 看起来会更直观

4年前 评论
yema

写法好几种 哪种可读性更强就用哪种 我一般用这种

     ->where(function ($query) {
         $query->where('name', 'name')
               ->orWhere('mobile_number', '=', 'mobile number')
               ->orWhere('email', '=', 'email')
               ->orWhere('score', '>', '1000');
     })
4年前 评论
DB::table('users')
     ->where('group_id', 'group id')
     ->when($param.length, function ($query) {
        $query->where(function ($query) use ($param) {
            $query->when($param['name'], function ($query) {$query->where('name', $param['name'])})
               ->when($param['mobile_number'], function ($query) {$query->orWhere('mobile_number', $param['mobile_number'])})
               ->when($param['email'], function ($query) {$query->orWhere('email', $param['email'])})
               ->when($param['score'], function ($query) {$query->orWhere('score', '>', $param['score'])});
        })
     })->get();
4年前 评论
  $orWhere = [];
  if ($params['name']) {
      $orWhere['name'] = $params['name'];
  }
  if ($params['mobile_number']) {
     $orWhere['mobile_number'] = $params['mobile_number'];
  }
  if ($params['email']) {
      $orWhere['email'] = $params['email'];
  }
  if ($params['score']) {
      $orWhere[] = ['score', '>', 1000, 'OR'];
  }

这种写法仿佛看到了ThinkPHP,一般就用普通的回调函数写法,也是文档上介绍的写法。

4年前 评论

这样写看起来很简洁,但实际可读性不高,不利于中大型项目,无形中增加了代码量。其次我个人认为,DB的写法偏TP5。而写出优雅易于理解的代码,远远要好于看起来炫酷,理解费时费力的方法。Eloquent ORM 这个可以去了解一下,相对TP5的模型,laravel这方面做得实在好太多了。感觉你把一个简单的查询写复杂了,如果稍微复杂一点,不知道你会怎么去写

4年前 评论
chuoke (楼主) 4年前
zulien (作者) 4年前
lun1bz 3年前
soulartist 2年前
zulien (作者) 2年前

这种新写法也不错,简洁明了

4年前 评论

不错,我喜欢用数组方式

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!