DB::table 與 查詢構(gòu)造器
若是不想使用原生的 sql 語句,我們可以使用 DB::table 語句,該語句會(huì)返回一個(gè) query 對(duì)象:
public function table($table)
{
? ? return $this->query()->from($table);
}
public function query()
{
? ? return new QueryBuilder(
? ? ? ? $this, $this->getQueryGrammar(), $this->getPostProcessor()
? ? );
}
我們可以看到,query 會(huì)有兩個(gè)成員,queryGrammar 與 postProcessor。queryGrammar 負(fù)責(zé)對(duì) QueryBuilcder 的結(jié)果進(jìn)行 sql 語言的轉(zhuǎn)化,postProcessor 負(fù)責(zé)查詢結(jié)果的后處理。
之所以 laravel 推薦我們使用查詢構(gòu)造器,而不是原生的 sql,原因在于可以避免 sql 注入漏洞。當(dāng)然,我們也可以在使用 DB::select() 函數(shù)中手動(dòng)寫 bindings 的值,但是這樣的話,我們寫 sql 的語句是就必須是這樣:
DB::select('select * from table where col=?',[1]);
必然會(huì)帶來很多不便。
有了查詢構(gòu)造器,我們就可以寫出 fluent 類型的語句:
DB::table('table')->select('*')->where('col', 1);
是不是很方便?
CRUD 與語法編譯器
相應(yīng)于 connection 對(duì)象的 CRUD,語法編譯器有 compileInsert、compileSelect、compileUpdate、compileDelete。其中最重要的是 compileSelect,因?yàn)樗粌H負(fù)責(zé)了 select 語句的語法編譯,還負(fù)責(zé)聚合語句 aggregate、from 語句、join 連接語句、wheres 條件語句、groups 分組語句、havings 條件語句、orders 排序語句、limit 語句、offset 語句、unions 聯(lián)合語句、lock 語句:
protected $selectComponents = [
? ? 'aggregate',
? ? 'columns',
? ? 'from',
? ? 'joins',
? ? 'wheres',
? ? 'groups',
? ? 'havings',
? ? 'orders',
? ? 'limit',
? ? 'offset',
? ? 'unions',
? ? 'lock',
];
public function compileSelect(Builder $query)
{
? ? $original = $query->columns;
? ? if (is_null($query->columns)) {
? ? ? ? $query->columns = ['*'];
? ? }
? ? $sql = trim($this->concatenate(
? ? ? ? $this->compileComponents($query))
? ? );
? ? $query->columns = $original;
? ? return $sql;
}
protected function compileComponents(Builder $query)
{
? ? $sql = [];
? ? foreach ($this->selectComponents as $component) {
? ? ? ? if (! is_null($query->$component)) {
? ? ? ? ? ? $method = 'compile'.ucfirst($component);
? ? ? ? ? ? $sql[$component] = $this->$method($query, $query->$component);
? ? ? ? }
? ? }
? ? return $sql;
}
可以看出來,語法編譯器會(huì)將上述所有的語句放入 $sql[] 成員中,然后通過 concatenate 函數(shù)組裝成 sql 語句:
protected function concatenate($segments)
{
? ? return implode(' ', array_filter($segments, function ($value) {
? ? ? ? return (string) $value !== '';
? ? }));
}
wrap 函數(shù)
若想要了解語法編譯器,我們就必須先要了解 grammer 中一個(gè)重要的函數(shù) wrap,這個(gè)函數(shù)專門對(duì)表名與列名進(jìn)行處理,
public function wrap($value, $prefixAlias = false)
{
? ? if ($this->isExpression($value)) {
? ? ? ? return $this->getValue($value);
? ? }
? ? if (strpos(strtolower($value), ' as ') !== false) {
? ? ? ? return $this->wrapAliasedValue($value, $prefixAlias);
? ? }
? ? return $this->wrapSegments(explode('.', $value));
}
處理的流程:
若是 Expression 對(duì)象,利用函數(shù) getValue 直接取出對(duì)象值,不對(duì)其進(jìn)行任何處理,用于處理原生 sql。expression 對(duì)象的作用是保護(hù)原始參數(shù),避免框架解析的一種方式。也就是說,當(dāng)我們用了 expression 來包裝參數(shù)的話,laravel 將不會(huì)對(duì)其進(jìn)行任何處理,包括庫名解析、表名前綴、別名等。
若表名 / 列名存在 as,則利用函數(shù) wrapAliasedValue 為表名設(shè)置別名。
若表名 / 列名含有 .,則會(huì)被分解為 庫名/表名,或者 表名/列名,并調(diào)用函數(shù) wrapSegments。
wrapAliasedValue 函數(shù)
wrapAliasedValue 函數(shù)用于處理別名:
protected function wrapAliasedValue($value, $prefixAlias = false)
{
? ? $segments = preg_split('/\s+as\s+/i', $value);
? ? if ($prefixAlias) {
? ? ? ? $segments[1] = $this->tablePrefix.$segments[1];
? ? }
? ? return $this->wrap(
? ? ? ? $segments[0]).' as '.$this->wrapValue($segments[1]
? ? );
}
可以看到,首先程序會(huì)根據(jù) as 將字符串分為兩部分,as 前的部分遞歸調(diào)用 wrap 函數(shù),as 后的部分調(diào)用 wrapValue 函數(shù).
wrapValue 函數(shù)
wrapValue 函數(shù)用來處理添加符號(hào) ",例如 table,會(huì)被這個(gè)函數(shù)變?yōu)?"table"。需要注意的是 table1"table2 這種情況,假如我們的數(shù)據(jù)庫中存在一個(gè)表,名字就叫做: table1"table2,我們?cè)跀?shù)據(jù)庫查詢的時(shí)候,必須將表名轉(zhuǎn)化為 "table1""table2",只有這樣,數(shù)據(jù)庫才會(huì)有效地轉(zhuǎn)化表名為 "table1"table2",否則數(shù)據(jù)庫就會(huì)報(bào)告錯(cuò)誤:找不到表。
protected function wrapValue($value)
{
? ? if ($value !== '*') {
? ? ? ? return '"'.str_replace('"', '""', $value).'"';
? ? }
? ? return $value;
}
wrapSegments 函數(shù)
wrapSegments 函數(shù)會(huì)判斷當(dāng)前參數(shù),如果是 table.column,會(huì)將前一部分 table 調(diào)用 wrapTable, column 調(diào)用 wrapValue,最后生成 “table”."column"。
protected function wrapSegments($segments)
{
? ? return collect($segments)->map(function ($segment, $key) use ($segments) {
? ? ? ? return $key == 0 && count($segments) > 1
? ? ? ? ? ? ? ? ? ? ? ? ? $this->wrapTable($segment)
? ? ? ? ? ? ? ? ? ? ? ? : $this->wrapValue($segment);
? ? })->implode('.');
}
wrapTable 函數(shù)
wrapTable 函數(shù)用于為數(shù)據(jù)表添加表前綴:
public function wrapTable($table)
{
? ? if (! $this->isExpression($table)) {
? ? ? ? return $this->wrap($this->tablePrefix.$table, true);
? ? }
? ? return $this->getValue($table);
}
wrap 整體流程圖如下:
from 語句
我們看到 DB::table 實(shí)際上是調(diào)用了查詢構(gòu)造器的 from 函數(shù)。接下來我們就看看,當(dāng)我們寫下了
DB::table('table')->get()
時(shí)發(fā)生了什么。
public function from($table)
{
? ? $this->from = $table;
? ? return $this;
}
我們看到,from 函數(shù)極其簡單,我們接下來看 get:
public function get($columns = ['*'])
{
? ? $original = $this->columns;
? ? if (is_null($original)) {
? ? ? ? $this->columns = $columns;
? ? }
? ? $results = $this->processor->processSelect($this, $this->runSelect());
? ? $this->columns = $original;
? ? return collect($results);
}
laravel 的查詢構(gòu)造器是懶加載的,只有調(diào)用了 get 函數(shù)才會(huì)真正的調(diào)用語法編譯器,采用調(diào)用底層 connection 對(duì)象進(jìn)行數(shù)據(jù)庫查詢:
protected function runSelect()
{
? ? return $this->connection->select(
? ? ? ? $this->toSql(), $this->getBindings(), ! $this->useWritePdo
? ? );
}
public function toSql()
{
? ? return $this->grammar->compileSelect($this);
}
compileFrom 函數(shù)
首先我們先看看流程圖:
語法編譯器 grammer 對(duì)于 from 語句的處理由函數(shù) compileFrom 負(fù)責(zé):
protected function compileFrom(Builder $query, $table)
{
? ? return 'from '.$this->wrapTable($table);
}
從流程圖可以看出,具體流程與 wrap 類似。
我們調(diào)用 from 時(shí),可以傳遞兩種參數(shù),一種是字符串,另一種是 expression 對(duì)象:
DB::table('table');
DB::table(new Expression('table'));
傳遞 expression 對(duì)象
當(dāng)我們傳遞 expression 對(duì)象的時(shí)候,grammer 就會(huì)調(diào)用 getValue 取出原生 sql 語句。
傳遞字符串
當(dāng)我們向 from 傳遞普通的字符串時(shí),laravel 就會(huì)對(duì)字符串調(diào)用 wrap 函數(shù)進(jìn)行處理,處理流程上一個(gè)小節(jié)已經(jīng)說明:
為表名加上前綴 $this->tablePrefix
若字符串存在 as,則為表名設(shè)置別名。
若字符串含有 .,則會(huì)被分解為 庫名 與 表名,并進(jìn)行分別調(diào)用 wrapTable 函數(shù)與 wrapValue 進(jìn)行處理。
為表名前后添加 ",例如 t1.t2 會(huì)被轉(zhuǎn)化為 "t1"."t2"(不同的數(shù)據(jù)庫添加的字符不同,mysql 就不是 ")
laravel 對(duì) from 處理流程存在一些問題,表名前綴設(shè)置功能與數(shù)據(jù)庫名功能公用存在問題,相關(guān) issue 地址是:[Bug] Table prefix added to database name when using database.table,有任何興趣的同學(xué)可以在這個(gè) issue 里面討論,或者直接向作者提 PR。若作者對(duì)此部分有任何修改,我會(huì)同步修改這篇文章。
Select 語句
本小節(jié)會(huì)介紹 select 語句:
public function select($columns = ['*'])
{
? ? $this->columns = is_array($columns) ? $columns : func_get_args();
? ? return $this;
}
queryBuilder 的 select 語句很簡單,我們不多討論.
selectRaw :
public function selectRaw($expression, array $bindings = [])
{
? ? $this->addSelect(new Expression($expression));
? ? if ($bindings) {
? ? ? ? $this->addBinding($bindings, 'select');
? ? }
? ? return $this;
}
public function addSelect($column)
{
? ? $column = is_array($column) ? $column : func_get_args();
? ? $this->columns = array_merge((array) $this->columns, $column);
? ? return $this;
}
可以看到, selectRaw 就是將 Expression 對(duì)象賦值到 columns 中,我們?cè)谇懊嬲f到,框架不會(huì)對(duì) Expression 進(jìn)行任何處理(更準(zhǔn)確的說是 wrap 函數(shù)),這樣就保證了原生語句的執(zhí)行。
我們接著看 grammer .
compileColumns 函數(shù)
protected function compileColumns(Builder $query, $columns)
{
? ? if (! is_null($query->aggregate)) {
? ? ? ? return;
? ? }
? ? $select = $query->distinct ? 'select distinct ' : 'select ';
? ? return $select.$this->columnize($columns);
}
public function columnize(array $columns)
{
? ? return implode(', ', array_map([$this, 'wrap'], $columns));
}
可以看到,grammer 對(duì) select 的語法編譯調(diào)用 wrap 函數(shù)對(duì)每個(gè) select 的字段進(jìn)行處理,處理過程在上面詳解過,在此不再贅述。
selectSub 語句
所謂的 select 子查詢,就是查詢的字段來源于其他數(shù)據(jù)表。對(duì)于這種查詢,可以分成兩部來理解,首先忽略整個(gè) select 子查詢,查出第一個(gè)表中的數(shù)據(jù),然后根據(jù)第一個(gè)表的數(shù)據(jù)執(zhí)行子查詢,
laravel 的 selectSub 支持閉包函數(shù)、queryBuild 對(duì)象或者原生 sql 語句,以下是單元測(cè)試樣例:
$query = DB::table('one')->select(['foo', 'bar'])->where('key', '=', 'val');
$query->selectSub(function ($query) {
? ? ? ? $query->from('two')->select('baz')->where('subkey', '=', 'subval');
? ? }, 'sub');
另一種寫法:
$query = DB::table('one')->select(['foo', 'bar'])->where('key', '=', 'val');
$query_sub = DB::table('one')->select('baz')->where('subkey', '=', 'subval');
$query->selectSub($query_sub, 'sub');
生成的 sql:
select "foo", "bar", (select "baz" from "two" where "subkey" = 'subval') as "sub" from "one" where "key" = 'val'
selectSub 語句的實(shí)現(xiàn)比較簡單:
public function selectSub($query, $as)
{
? ? if ($query instanceof Closure) {
? ? ? ? $callback = $query;
? ? ? ? $callback($query = $this->forSubQuery());
? ? }
? ? list($query, $bindings) = $this->parseSubSelect($query);
? ? return $this->selectRaw(
? ? ? ? '('.$query.') as '.$this->grammar->wrap($as), $bindings
? ? );
}
protected function parseSubSelect($query)
{
? ? if ($query instanceof self) {
? ? ? ? $query->columns = [$query->columns[0]];
? ? ? ? return [$query->toSql(), $query->getBindings()];
? ? } elseif (is_string($query)) {
? ? ? ? return [$query, []];
? ? } else {
? ? ? ? throw new InvalidArgumentException;
? ? }
}
可以看到,如果 selectSub 的參數(shù)是閉包函數(shù),那么就會(huì)先執(zhí)行閉包函數(shù),閉包函數(shù)將會(huì)為 query 根據(jù)查詢語句更新對(duì)象。
parseSubSelect 函數(shù)為子查詢解析 sql 語句與 binding 變量。
where 語句總結(jié)
在 laravel 文檔中,queryBuild 的用法很詳盡,但是為了更好的理解源碼,我們?cè)谶@里再次大概的總結(jié)一下:
基礎(chǔ)用法
users = DB::table('users')->where('votes', '=', 100)->get();
$users = DB::table('users')->where('votes', 100)->get();
這兩個(gè)是等價(jià)的寫法。
where 數(shù)組
$users = DB::table('users')->where(
? ? ['status' => 1, 'subscribed' => 1],
)->get();
$users = DB::table('users')->where([
? ? ['status', '1'],
? ? ['subscribed', '1'],
])->get();
$users = DB::table('users')->where([
? ? ['status', '1'],
? ? ['subscribed', '<>', '1'],
])->get();
where 查詢組
DB::table('users')
? ? ->where('name', '=', 'John')
? ? ->orWhere(function ($query) {
? ? ? ? $query->where('votes', '>', 100)
? ? ? ? ? ? ? ->where('title', '<>', 'Admin');
? ? })
? ? ->get();
這一句的 sql 語句是
select * from users where name = 'John' or (votes > 100 and title <> 'Admin')
where 子查詢
public function testFullSubSelects()
{
? ? $builder = $this->getBuilder();
? ? DB::table('users')
? ? ? ? ->Where('id', '=', function ($q) {
? ? ? ? $q->select(new Raw('max(id)'))->from('users')->where('email', '=', 'bar');
? ? });
}
這一句的 sql 語句是
select * from "users" where "email" = foo or "id" = (select max(id) from "users" where "email" = bar`
orWhere
$users = DB::table('users')
? ? ? ? ? ? ->where('votes', '>', 100)
? ? ? ? ? ? ->orWhere('name', 'John')
? ? ? ? ? ? ->get();
whereDate / whereMonth / whereDay / whereYear / whereTime
$users = DB::table('users')
? ? ? ? ? ? ->whereDate('created_at', '2016-12-31')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereMonth('created_at', '12')
? ? ? ? ? ? ->get();? ? ? ? ? ?
$users = DB::table('users')
? ? ? ? ? ? ->whereDay('created_at', '31')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereYear('created_at', '2016')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereTime('created_at', '>=', '22:00')
? ? ? ? ? ? ->get();
whereBetween / whereNotBetween
$users = DB::table('users')
? ? ? ? ? ? ->whereBetween('votes', [1, 100])->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereNotBetween('votes', [1, 100])
? ? ? ? ? ? ->get();
whereRaw / orWhereRaw
$users = DB::table('users')
? ? ? ? ? ? ->whereRaw('id = ? or email = ?', [1, 'foo'])
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->orWhereRaw('id = ? or email = ?', [1, 'foo'])
? ? ? ? ? ? ->get();
whereIn / whereNotIn / orWhereIn / orWhereNotIn
$users = DB::table('users')
? ? ? ? ? ? ->whereIn('id', [1, 2, 3])
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereNotIn('id', [1, 2, 3])
? ? ? ? ? ? ->get();
$users = DB::table('users')? ? ? ? ? ?
? ? ? ? ? ? ? ? ->whereIn('id', function ($q) {
? ? ? ? ? ? ? ? ? ? $q->select('id')->from('users')->where('age', '>', 25)->take(3);
? ? ? ? ? ? ? ? });? ? ?
$users = DB::table('users')? ? ? ? ? ?
? ? ? ? ? ? ? ? ->whereNotIn('id', function ($q) {
? ? ? ? ? ? ? ? ? ? $q->select('id')->from('users')->where('age', '>', 25)->take(3);
? ? ? ? ? ? ? ? });?
$query = DB::table('users')->select('id')->where('age', '>', 25)->take(3);
$users = DB::table('users')->whereIn('id', $query);?
$users = DB::table('users')->whereNotIn('id', $query);
有意思的是,當(dāng)我們?cè)?whereIn / whereNotIn / orWhereIn / orWhereNotIn 中傳入空數(shù)組的時(shí)候:
$users = DB::table('users')
? ? ? ? ? ? ->whereIn('id', [])
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->orWhereIn('id', [])
? ? ? ? ? ? ->get();
這個(gè)時(shí)候,框架自動(dòng)會(huì)生成如下的 sql:
select * from "users" where 0 = 1;
select * from "users" where "id" = ? or 0 = 1
whereColumn
$users = DB::table('users')
? ? ? ? ? ? ->whereColumn('first_name', 'last_name')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereColumn('updated_at', '>', 'created_at')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereColumn([
? ? ? ? ? ? ? ? ['first_name', '=', 'last_name'],
? ? ? ? ? ? ? ? ['updated_at', '>', 'created_at']
? ? ? ? ? ? ])->get();? ? ? ? ? ?
whereNull / whereNotNull / orWhereNull / orWhereNotNull
$users = DB::table('users')
? ? ? ? ? ? ->whereNull('updated_at')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->whereNotNull('updated_at')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->orWhereNull('updated_at')
? ? ? ? ? ? ->get();
$users = DB::table('users')
? ? ? ? ? ? ->orWhereNotNull('updated_at')
? ? ? ? ? ? ->get();? ? ? ? ? ? ? ? ? ? ? ?
whereExists / whereNotExists / orWhereExists / orWhereNotExists
DB::table('users')
? ? ->whereExists(function ($query) {
? ? ? ? $query->select(DB::raw(1))
? ? ? ? ? ? ? ->from('orders')
? ? ? ? ? ? ? ->whereRaw('orders.user_id = users.id');
? ? })
? ? ->get();
// select * from users where exists ( select 1 from orders where orders.user_id = users.id)
DB::table('users')
? ? ->whereNotExists(function ($query) {
? ? ? ? $query->select(DB::raw(1))
? ? ? ? ? ? ? ->from('orders')
? ? ? ? ? ? ? ->whereRaw('orders.user_id = users.id');
? ? })
? ? ->get();
// select * from users where not exists ( select 1 from orders where orders.user_id = users.id)
DB::table('users')
? ? ->orWhereExists(function ($query) {
? ? ? ? $query->select(DB::raw(1))
? ? ? ? ? ? ? ->from('orders')
? ? ? ? ? ? ? ->whereRaw('orders.user_id = users.id');
? ? })
? ? ->get();
// select * from users or exists ( select 1 from orders where orders.user_id = users.id)
DB::table('users')
? ? ->orWhereNotExists(function ($query) {
? ? ? ? $query->select(DB::raw(1))
? ? ? ? ? ? ? ->from('orders')
? ? ? ? ? ? ? ->whereRaw('orders.user_id = users.id');
? ? })
? ? ->get();?
// select * from users or not exists ( select 1 from orders where orders.user_id = users.id)? ? ? ? ? ?
where 函數(shù)
我們首先先看看源碼:
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
? ? if (is_array($column)) {
? ? ? ? return $this->addArrayOfWheres($column, $boolean);
? ? }
? ? list($value, $operator) = $this->prepareValueAndOperator(
? ? ? ? $value, $operator, func_num_args() == 2
? ? );
? ? if ($column instanceof Closure) {
? ? ? ? return $this->whereNested($column, $boolean);
? ? }
? ? if ($this->invalidOperator($operator)) {
? ? ? ? list($value, $operator) = [$operator, '='];
? ? }
? ? if ($value instanceof Closure) {
? ? ? ? return $this->whereSub($column, $operator, $value, $boolean);
? ? }
? ? if (is_null($value)) {
? ? ? ? return $this->whereNull($column, $boolean, $operator !== '=');
? ? }
? ? if (Str::contains($column, '->') && is_bool($value)) {
? ? ? ? $value = new Expression($value ? 'true' : 'false');
? ? }
? ? $type = 'Basic';
? ? $this->wheres[] = compact(
? ? ? ? 'type', 'column', 'operator', 'value', 'boolean'
? ? );
? ? if (! $value instanceof Expression) {
? ? ? ? $this->addBinding($value, 'where');
? ? }
? ? return $this;
}
可以看到,為了支持框架的多種 where 形式,where 的代碼中寫了很多的條件語句。我們接下來一個(gè)個(gè)分析。
grammer——compileWheres 函數(shù)
在此之前,我們先看看語法編譯器對(duì) where 查詢的處理:
protected function compileWheres(Builder $query)
{
? ? if (is_null($query->wheres)) {
? ? ? ? return '';
? ? }
? ? if (count($sql = $this->compileWheresToArray($query)) > 0) {
? ? ? ? return $this->concatenateWhereClauses($query, $sql);
? ? }
? ? return '';
}
compileWheres 函數(shù)負(fù)責(zé)所有 where 查詢條件的語法編譯工作,compileWheresToArray 函數(shù)負(fù)責(zé)循環(huán)編譯查詢條件,concatenateWhereClauses 函數(shù)負(fù)責(zé)將多個(gè)查詢條件合并。
protected function compileWheresToArray($query)
{
? ? return collect($query->wheres)->map(function ($where) use ($query) {
? ? ? ? return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where);
? ? })->all();
}
protected function concatenateWhereClauses($query, $sql)
{
? ? $conjunction = $query instanceof JoinClause ? 'on' : 'where';
? ? return $conjunction.' '.$this->removeLeadingBoolean(implode(' ', $sql));
}
compileWheresToArray 函數(shù)負(fù)責(zé)把 $query->wheres 中多個(gè) where 條件循環(huán)起來:
$where['boolean'] 是多個(gè)查詢條件的連接,and 或者 or,一般 where 條件默認(rèn)為 and, 各種 orWhere 的連接是 or
where{$where['type']} 是查詢的類型,laravel 把查詢條件分為以下幾類:base、raw、in、notIn、inSub、notInSub、null、notNull、between、column、nested、sub、exist、notExist。每種類型的查詢條件都有對(duì)應(yīng)的 grammer 方法
concatenateWhereClauses 函數(shù)負(fù)責(zé)連接所有的搜索條件,由于 join 的連接條件也會(huì)調(diào)用 compileWheres 函數(shù),所以會(huì)有判斷是否是真正的 where 查詢,
where 數(shù)組
如果 column 是數(shù)組的話,就會(huì)調(diào)用:
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);
}
可以看到,數(shù)組分為兩類,一種是列名為 key,例如 ['foo' => 1, 'bar' => 2],這個(gè)時(shí)候就是調(diào)用 query->where('foo', '=', '1', ‘a(chǎn)nd’)。還有一種是 [['foo','1'],['bar','2']],這個(gè)時(shí)候就會(huì)調(diào)用 $query->where(['foo','1'])。
public function whereNested(Closure $callback, $boolean = 'and')
{
? ? call_user_func($callback, $query = $this->forNestedWhere());
? ? return $this->addNestedWhereQuery($query, $boolean);
}
public function addNestedWhereQuery($query, $boolean = 'and')
{
? ? if (count($query->wheres)) {
? ? ? ? $type = 'Nested';
? ? ? ? $this->wheres[] = compact('type', 'query', 'boolean');
? ? ? ? $this->addBinding($query->getBindings(), 'where');
? ? }
? ? return $this;
}
grammer——whereNested
語法編譯器中負(fù)責(zé)查詢組的函數(shù)是 whereNested,它會(huì)取出 where 中的 query,遞歸調(diào)用 compileWheres 函數(shù)
protected function whereNested(Builder $query, $where)
{
? ? $offset = $query instanceof JoinClause ? 3 : 6;
? ? return '('.substr($this->compileWheres($where['query']), $offset).')';
}
由于 compileWheres 會(huì)返回 where ... 或者 on ... 等開頭的 sql 語句,所以我們需要把返回結(jié)果截取前 3 個(gè)字符或 6 個(gè)字符。
where 查詢組
若查詢條件是一個(gè)閉包函數(shù),也就是第一個(gè)參數(shù) column 是個(gè)閉包函數(shù),那么就要調(diào)用 whereNested 函數(shù),過程和上述過程一致。
whereSub 子查詢
如果第二個(gè)參數(shù)或者第三個(gè)參數(shù)是一個(gè)閉包函數(shù)的話,就是 where 子查詢語句,這時(shí)需要調(diào)用 whereSub 函數(shù):
protected function whereSub($column, $operator, Closure $callback, $boolean)
{
? ? $type = 'Sub';
? ? call_user_func($callback, $query = $this->forSubQuery());
? ? $this->wheres[] = compact(
? ? ? ? 'type', 'column', 'operator', 'query', 'boolean'
? ? );
? ? $this->addBinding($query->getBindings(), 'where');
? ? return $this;
}
grammer——whereSub
grammer 中負(fù)責(zé)子查詢的是 whereSub 函數(shù):
protected function whereSub(Builder $query, $where)
{
? ? $select = $this->compileSelect($where['query']);
? ? return $this->wrap($where['column']).' '.$where['operator']." ($select)";
}
因?yàn)樽硬樵冎锌梢源嬖?select 、where 、join 等一切 sql 語句,所以遞歸的是 compileSelect 這個(gè)大的函數(shù),而不是僅僅 compileWheres。
whereNull 語句
whereNull 函數(shù)也很簡單:
public function whereNull($column, $boolean = 'and', $not = false)
{
? ? $type = $not ? 'NotNull' : 'Null';
? ? $this->wheres[] = compact('type', 'column', 'boolean');
? ? return $this;
}
grammer——whereNull 函數(shù)
protected function whereNull(Builder $query, $where)
{
? ? return $this->wrap($where['column']).' is null';
}
whereBasic 語句
如果上述情況都不符合,那么就是最基礎(chǔ)的 where 語句,類型是 basic.
$type = 'Basic';
$this->wheres[] = compact(
? ? 'type', 'column', 'operator', 'value', 'boolean'
);
if (! $value instanceof Expression) {
? ? $this->addBinding($value, 'where');
}
grammer——whereBasic 函數(shù)
grammer 中最基礎(chǔ)的 where 語句由 wherebasic 函數(shù)負(fù)責(zé):
protected function whereBasic(Builder $query, $where)
{
? ? $value = $this->parameter($where['value']);
? ? return $this->wrap($where['column']).' '.$where['operator'].' '.$value;
}
public function parameter($value)
{
? ? return $this->isExpression($value) ? $this->getValue($value) : '?';
}
wherebasic 函數(shù)對(duì)參數(shù)進(jìn)行了替換,利用 ? 來替換真正的值。
orWhere 語句
orWhere 函數(shù)只是在 where 函數(shù)的基礎(chǔ)上固定了最后一個(gè)參數(shù):
public function orWhere($column, $operator = null, $value = null)
{
? ? return $this->where($column, $operator, $value, 'or');
}
whereColumn 語句
whereColumn 函數(shù)是簡化版的 where 函數(shù),只是 where 類型不是 basic,而是 column:
public function whereColumn($first, $operator = null, $second = null, $boolean = 'and')
{
? ? if (is_array($first)) {
? ? ? ? return $this->addArrayOfWheres($first, $boolean, 'whereColumn');
? ? }
? ? if ($this->invalidOperator($operator)) {
? ? ? ? list($second, $operator) = [$operator, '='];
? ? }
? ? $type = 'Column';
? ? $this->wheres[] = compact(
? ? ? ? 'type', 'first', 'operator', 'second', 'boolean'
? ? );
? ? return $this;
}
grammer——whereColumn
可以看到 whereColumn 與 whereBasic 的區(qū)別是對(duì) value 的不同處理,whereBasic 實(shí)際上是將其看作值,需要用 ? 來替換,參數(shù)加載到 binding 中去的。而 whereColumn 是將 second 當(dāng)做列名來處理,是需要經(jīng)過表名、別名等處理的:
protected function whereColumn(Builder $query, $where)
{
? ? return $this->wrap($where['first']).' '.$where['operator'].' '.$this->wrap($where['second']);
}
whereIn 語句
public function whereIn($column, $values, $boolean = 'and', $not = false)
{
? ? $type = $not ? 'NotIn' : 'In';
? ? if ($values instanceof EloquentBuilder) {
? ? ? ? $values = $values->getQuery();
? ? }
? ? if ($values instanceof self) {
? ? ? ? return $this->whereInExistingQuery(
? ? ? ? ? ? $column, $values, $boolean, $not
? ? ? ? );
? ? }
? ? if ($values instanceof Closure) {
? ? ? ? return $this->whereInSub($column, $values, $boolean, $not);
? ? }
? ? if ($values instanceof Arrayable) {
? ? ? ? $values = $values->toArray();
? ? }
? ? $this->wheres[] = compact('type', 'column', 'values', 'boolean');
? ? foreach ($values as $value) {
? ? ? ? if (! $value instanceof Expression) {
? ? ? ? ? ? $this->addBinding($value, 'where');
? ? ? ? }
? ? }
? ? return $this;
}
可以看出來,whereIn 支持四種參數(shù): EloquentBuilder、queryBuilder、Closure、Arrayable。
whereInExistingQuery 函數(shù)
當(dāng) whereIn 第二個(gè)參數(shù)是 queryBuild 時(shí),就會(huì)調(diào)用 whereInExistingQuery 函數(shù):
protected function whereInExistingQuery($column, $query, $boolean, $not)
{
? ? $type = $not ? 'NotInSub' : 'InSub';
? ? $this->wheres[] = compact('type', 'column', 'query', 'boolean');
? ? $this->addBinding($query->getBindings(), 'where');
? ? return $this;
}
可以看出,這個(gè)函數(shù)添加了一個(gè)類型為 InSub 或 NotInSub 類型的 where,我們接著在語法編譯器來看:
grammer——whereInSub / whereNotInSub
whereInSub / whereNotInSub 與 whereSub 類似,只是 operator 被固定成 in / not in 而已:
protected function whereInSub(Builder $query, $where)
{
? ? return $this->wrap($where['column']).' in ('.$this->compileSelect($where['query']).')';
}
protected function whereNotInSub(Builder $query, $where)
{
? ? return $this->wrap($where['column']).' not in ('.$this->compileSelect($where['query']).')';
}
whereInSub 函數(shù)
當(dāng) whereIn 第二個(gè)參數(shù)是閉包函數(shù)的時(shí)候,就會(huì)調(diào)用 whereInSub 函數(shù):
protected function whereInSub($column, Closure $callback, $boolean, $not)
{
? ? $type = $not ? 'NotInSub' : 'InSub';
? ? call_user_func($callback, $query = $this->forSubQuery());
? ? $this->wheres[] = compact('type', 'column', 'query', 'boolean');
? ? $this->addBinding($query->getBindings(), 'where');
? ? return $this;
}
可以看出來,除了閉包函數(shù)需要執(zhí)行獲得 query 對(duì)象之外,whereInSub 函數(shù)與 whereInExistingQuery 函數(shù)一致。
In / NotIn 類型 where
如果參數(shù)傳遞了數(shù)組,那么就會(huì)創(chuàng)建 In 或者 NotIn 類型的 where:
$this->wheres[] = compact('type', 'column', 'values', 'boolean');
foreach ($values as $value) {
? ? if (! $value instanceof Expression) {
? ? ? ? $this->addBinding($value, 'where');
? ? }
}
grammer——whereIn / whereNotIn
whereIn 或者 whereNotIn 與 whereBasic 函數(shù)基本一致,只是參數(shù)值需要循環(huán)調(diào)用 parameter 函數(shù):
protected function whereIn(Builder $query, $where)
{
? ? if (! empty($where['values'])) {
? ? ? ? return $this->wrap($where['column']).' in ('.$this->parameterize($where['values']).')';
? ? }
? ? return '0 = 1';
}
protected function whereNotIn(Builder $query, $where)
{
? ? if (! empty($where['values'])) {
? ? ? ? return $this->wrap($where['column']).' not in ('.$this->parameterize($where['values']).')';
? ? }
? ? return '1 = 1';
}
public function parameterize(array $values)
{
? ? return implode(', ', array_map([$this, 'parameter'], $values));
}
whereBetween 語句
類似的 whereBetween 創(chuàng)建了新的 between 類型的 where:
public function whereBetween($column, array $values, $boolean = 'and', $not = false)
{
? ? $type = 'between';
? ? $this->wheres[] = compact('column', 'type', 'boolean', 'not');
? ? $this->addBinding($values, 'where');
? ? return $this;
}
grammer——whereBetween
有意思的是,whereBetween 不支持原生的參數(shù)值,也就是不支持 expression 對(duì)象,所以直接用 ? 來代替參數(shù)值:
protected function whereBetween(Builder $query, $where)
{
? ? $between = $where['not'] ? 'not between' : 'between';
? ? return $this->wrap($where['column']).' '.$between.' ? and ?';
}
whereExist 語句
whereExist 只支持閉包函數(shù)作為子查詢語句,和之前一樣,創(chuàng)建了 Exist 或者 NotExist 類型的 where
public function whereExists(Closure $callback, $boolean = 'and', $not = false)
{
? ? $query = $this->forSubQuery();
? ? call_user_func($callback, $query);
? ? return $this->addWhereExistsQuery($query, $boolean, $not);
}
public function addWhereExistsQuery(Builder $query, $boolean = 'and', $not = false)
{
? ? $type = $not ? 'NotExists' : 'Exists';
? ? $this->wheres[] = compact('type', 'operator', 'query', 'boolean');
? ? $this->addBinding($query->getBindings(), 'where');
? ? return $this;
}
grammer——whereExists / whereNotExists
可以看到,這部分的語法編譯器仍然和 whereSub 非常類似:
protected function whereExists(Builder $query, $where)
{
? ? return 'exists ('.$this->compileSelect($where['query']).')';
}
protected function whereNotExists(Builder $query, $where)
{
? ? return 'not exists ('.$this->compileSelect($where['query']).')';
}
whereDate / whereMonth / whereDay / whereYear / whereTime
關(guān)于時(shí)間的查詢條件都由函數(shù) addDateBasedWhere 負(fù)責(zé)創(chuàng)建新的類型的 where,由于這些函數(shù)大致相同,我這里只貼一種:
protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
{
? ? $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
? ? $this->addBinding($value, 'where');
? ? return $this;
}
public function whereDate($column, $operator, $value = null, $boolean = 'and')
{
? ? list($value, $operator) = $this->prepareValueAndOperator(
? ? ? ? $value, $operator, func_num_args() == 2
? ? );
? ? return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
}
...
grammer——dateBasedWhere
時(shí)間查詢條件都是利用數(shù)據(jù)庫的 date、month、day、year、time 函數(shù)實(shí)現(xiàn)的:
protected function whereTime(Builder $query, $where)
{
? ? return $this->dateBasedWhere('time', $query, $where);
}
protected function dateBasedWhere($type, Builder $query, $where)
{
? ? $value = $this->parameter($where['value']);
? ? return $type.'('.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
}
whereRaw 語句
原生的 sql 語句及其簡單:
public function whereRaw($sql, $bindings = [], $boolean = 'and')
{
? ? $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean];
? ? $this->addBinding((array) $bindings, 'where');
? ? return $this;
}
語法編譯器工作:
protected function whereRaw(Builder $query, $where)
{
? ? return $where['sql'];
}
————————————————
原文作者:leoyang
轉(zhuǎn)自鏈接:https://learnku.com/articles/6204/laravel-database-query-constructor-and-syntax-compiler-source-code-analysis-part-1
版權(quán)聲明:著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)保留以上作者信息和原文鏈接。