我使用 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
)
)
虽然很多人都知道这个,我还是希望这能带来些许启发。
那么还有没有更多更灵活的方式呢?
————————————————
原文作者:chuoke
转自链接:https://learnku.com/articles/36743
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。