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
本帖由系统于 3年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8
DB::table('users')
     ->where('group_id', 'group id')
     ->when($params['name'], function($query) use ($params['name']) {
         $query->orWhere('name', $params['name']);
     });

这个写法也可以吧

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

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

5年前 评论
yema

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

     ->where(function ($query) {
         $query->where('name', 'name')
               ->orWhere('mobile_number', '=', 'mobile number')
               ->orWhere('email', '=', 'email')
               ->orWhere('score', '>', '1000');
     })
5年前 评论
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();
5年前 评论
  $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,一般就用普通的回调函数写法,也是文档上介绍的写法。

5年前 评论

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

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

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

5年前 评论

不错,我喜欢用数组方式

5年前 评论