Laravel Database——查詢構(gòu)造器與語法編譯器源碼分析 (上)

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)保留以上作者信息和原文鏈接。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容