我使用 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
)
)
雖然很多人都知道這個,我還是希望這能帶來些許啟發。
那麼還有沒有更多更靈活的方式呢?