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

DB::table 與 查詢構造器

若是不想使用原生的 sql 語句夭坪,我們可以使用 DB::table 語句怒见,該語句會返回一個 query 對象:

public function table($table)

{

? ? return $this->query()->from($table);

}

public function query()

{

? ? return new QueryBuilder(

? ? ? ? $this, $this->getQueryGrammar(), $this->getPostProcessor()

? ? );

}

我們可以看到羞芍,query 會有兩個成員址遇,queryGrammar 與 postProcessor哗咆。queryGrammar 負責對 QueryBuilcder 的結(jié)果進行 sql 語言的轉(zhuǎn)化咽扇,postProcessor 負責查詢結(jié)果的后處理。

之所以 laravel 推薦我們使用查詢構造器跪妥,而不是原生的 sql鞋喇,原因在于可以避免 sql 注入漏洞。當然眉撵,我們也可以在使用 DB::select() 函數(shù)中手動寫 bindings 的值侦香,但是這樣的話,我們寫 sql 的語句是就必須是這樣:

DB::select('select * from table where col=?',[1]);

必然會帶來很多不便纽疟。

有了查詢構造器罐韩,我們就可以寫出 fluent 類型的語句:

DB::table('table')->select('*')->where('col', 1);

是不是很方便?


CRUD 與語法編譯器

相應于 connection 對象的 CRUD污朽,語法編譯器有 compileInsert散吵、compileSelect、compileUpdate蟆肆、compileDelete矾睦。其中最重要的是 compileSelect,因為它不僅負責了 select 語句的語法編譯炎功,還負責聚合語句 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;

}

可以看出來居灯,語法編譯器會將上述所有的語句放入 $sql[] 成員中,然后通過 concatenate 函數(shù)組裝成 sql 語句:

protected function concatenate($segments)

{

? ? return implode(' ', array_filter($segments, function ($value) {

? ? ? ? return (string) $value !== '';

? ? }));

}

wrap 函數(shù)

若想要了解語法編譯器内狗,我們就必須先要了解 grammer 中一個重要的函數(shù) wrap穆壕,這個函數(shù)專門對表名與列名進行處理,

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 對象其屏,利用函數(shù) getValue 直接取出對象值,不對其進行任何處理缨该,用于處理原生 sql偎行。expression 對象的作用是保護原始參數(shù),避免框架解析的一種方式贰拿。也就是說蛤袒,當我們用了 expression 來包裝參數(shù)的話,laravel 將不會對其進行任何處理膨更,包括庫名解析妙真、表名前綴、別名等荚守。

若表名 / 列名存在 as珍德,則利用函數(shù) wrapAliasedValue 為表名設置別名。

若表名 / 列名含有 .矗漾,則會被分解為 庫名/表名锈候,或者 表名/列名,并調(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]

? ? );

}

可以看到泵琳,首先程序會根據(jù) as 將字符串分為兩部分,as 前的部分遞歸調(diào)用 wrap 函數(shù)誊役,as 后的部分調(diào)用 wrapValue 函數(shù).

wrapValue 函數(shù)

wrapValue 函數(shù)用來處理添加符號 "获列,例如 table,會被這個函數(shù)變?yōu)?"table"蛔垢。需要注意的是 table1"table2 這種情況击孩,假如我們的數(shù)據(jù)庫中存在一個表,名字就叫做: table1"table2啦桌,我們在數(shù)據(jù)庫查詢的時候溯壶,必須將表名轉(zhuǎn)化為 "table1""table2"及皂,只有這樣,數(shù)據(jù)庫才會有效地轉(zhuǎn)化表名為 "table1"table2"且改,否則數(shù)據(jù)庫就會報告錯誤:找不到表验烧。

protected function wrapValue($value)

{

? ? if ($value !== '*') {

? ? ? ? return '"'.str_replace('"', '""', $value).'"';

? ? }

? ? return $value;

}

wrapSegments 函數(shù)

wrapSegments 函數(shù)會判斷當前參數(shù),如果是 table.column又跛,會將前一部分 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 實際上是調(diào)用了查詢構造器的 from 函數(shù)慨蓝。接下來我們就看看感混,當我們寫下了

DB::table('table')->get()

時發(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 的查詢構造器是懶加載的,只有調(diào)用了 get 函數(shù)才會真正的調(diào)用語法編譯器此熬,采用調(diào)用底層 connection 對象進行數(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 對于 from 語句的處理由函數(shù) compileFrom 負責:

protected function compileFrom(Builder $query, $table)

{

? ? return 'from '.$this->wrapTable($table);

}

從流程圖可以看出庭呜,具體流程與 wrap 類似。

我們調(diào)用 from 時犀忱,可以傳遞兩種參數(shù)募谎,一種是字符串,另一種是 expression 對象:

DB::table('table');

DB::table(new Expression('table'));

傳遞 expression 對象

當我們傳遞 expression 對象的時候阴汇,grammer 就會調(diào)用 getValue 取出原生 sql 語句数冬。

傳遞字符串

當我們向 from 傳遞普通的字符串時,laravel 就會對字符串調(diào)用 wrap 函數(shù)進行處理搀庶,處理流程上一個小節(jié)已經(jīng)說明:

為表名加上前綴 $this->tablePrefix

若字符串存在 as拐纱,則為表名設置別名。

若字符串含有 .地来,則會被分解為 庫名 與 表名戳玫,并進行分別調(diào)用 wrapTable 函數(shù)與 wrapValue 進行處理。

為表名前后添加 "未斑,例如 t1.t2 會被轉(zhuǎn)化為 "t1"."t2"(不同的數(shù)據(jù)庫添加的字符不同咕宿,mysql 就不是 ")

laravel 對 from 處理流程存在一些問題,表名前綴設置功能與數(shù)據(jù)庫名功能公用存在問題蜡秽,相關 issue 地址是:[Bug] Table prefix added to database name when using database.table府阀,有任何興趣的同學可以在這個 issue 里面討論,或者直接向作者提 PR芽突。若作者對此部分有任何修改试浙,我會同步修改這篇文章。


Select 語句

本小節(jié)會介紹 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 對象賦值到 columns 中钠糊,我們在前面說到,框架不會對 Expression 進行任何處理(更準確的說是 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 對 select 的語法編譯調(diào)用 wrap 函數(shù)對每個 select 的字段進行處理管宵,處理過程在上面詳解過截珍,在此不再贅述。


selectSub 語句

所謂的 select 子查詢箩朴,就是查詢的字段來源于其他數(shù)據(jù)表岗喉。對于這種查詢,可以分成兩部來理解炸庞,首先忽略整個 select 子查詢钱床,查出第一個表中的數(shù)據(jù),然后根據(jù)第一個表的數(shù)據(jù)執(zhí)行子查詢埠居,

laravel 的 selectSub 支持閉包函數(shù)诞丽、queryBuild 對象或者原生 sql 語句,以下是單元測試樣例:

$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 語句的實現(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ù),那么就會先執(zhí)行閉包函數(shù)刑赶,閉包函數(shù)將會為 query 根據(jù)查詢語句更新對象捏浊。

parseSubSelect 函數(shù)為子查詢解析 sql 語句與 binding 變量。


where 語句總結(jié)

在 laravel 文檔中撞叨,queryBuild 的用法很詳盡金踪,但是為了更好的理解源碼,我們在這里再次大概的總結(jié)一下:

基礎用法

users = DB::table('users')->where('votes', '=', 100)->get();

$users = DB::table('users')->where('votes', 100)->get();

這兩個是等價的寫法牵敷。

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);

有意思的是胡岔,當我們在 whereIn / whereNotIn / orWhereIn / orWhereNotIn 中傳入空數(shù)組的時候:

$users = DB::table('users')

? ? ? ? ? ? ->whereIn('id', [])

? ? ? ? ? ? ->get();

$users = DB::table('users')

? ? ? ? ? ? ->orWhereIn('id', [])

? ? ? ? ? ? ->get();

這個時候,框架自動會生成如下的 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 的代碼中寫了很多的條件語句。我們接下來一個個分析毛肋。

grammer——compileWheres 函數(shù)

在此之前怨咪,我們先看看語法編譯器對 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ù)負責所有 where 查詢條件的語法編譯工作,compileWheresToArray 函數(shù)負責循環(huán)編譯查詢條件润匙,concatenateWhereClauses 函數(shù)負責將多個查詢條件合并诗眨。

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ù)負責把 $query->wheres 中多個 where 條件循環(huán)起來:

$where['boolean'] 是多個查詢條件的連接,and 或者 or孕讳,一般 where 條件默認為 and, 各種 orWhere 的連接是 or

where{$where['type']} 是查詢的類型匠楚,laravel 把查詢條件分為以下幾類:base巍膘、raw、in芋簿、notIn峡懈、inSub、notInSub益咬、null逮诲、notNull、between幽告、column梅鹦、nested、sub冗锁、exist齐唆、notExist。每種類型的查詢條件都有對應的 grammer 方法

concatenateWhereClauses 函數(shù)負責連接所有的搜索條件冻河,由于 join 的連接條件也會調(diào)用 compileWheres 函數(shù)箍邮,所以會有判斷是否是真正的 where 查詢,

where 數(shù)組

如果 column 是數(shù)組的話叨叙,就會調(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]味滞,這個時候就是調(diào)用 query->where('foo', '=', '1', ‘a(chǎn)nd’)。還有一種是 [['foo','1'],['bar','2']]钮呀,這個時候就會調(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

語法編譯器中負責查詢組的函數(shù)是 whereNested,它會取出 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 會返回 where ... 或者 on ... 等開頭的 sql 語句蚁署,所以我們需要把返回結(jié)果截取前 3 個字符或 6 個字符。

where 查詢組

若查詢條件是一個閉包函數(shù)蚂四,也就是第一個參數(shù) column 是個閉包函數(shù)光戈,那么就要調(diào)用 whereNested 函數(shù),過程和上述過程一致遂赠。

whereSub 子查詢

如果第二個參數(shù)或者第三個參數(shù)是一個閉包函數(shù)的話田度,就是 where 子查詢語句,這時需要調(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 中負責子查詢的是 whereSub 函數(shù):

protected function whereSub(Builder $query, $where)

{

? ? $select = $this->compileSelect($where['query']);

? ? return $this->wrap($where['column']).' '.$where['operator']." ($select)";

}

因為子查詢中可以存在 select 解愤、where 镇饺、join 等一切 sql 語句,所以遞歸的是 compileSelect 這個大的函數(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 語句

如果上述情況都不符合作儿,那么就是最基礎的 where 語句中跌,類型是 basic.

$type = 'Basic';

$this->wheres[] = compact(

? ? 'type', 'column', 'operator', 'value', 'boolean'

);

if (! $value instanceof Expression) {

? ? $this->addBinding($value, 'where');

}

grammer——whereBasic 函數(shù)

grammer 中最基礎的 where 語句由 wherebasic 函數(shù)負責:

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ù)對參數(shù)進行了替換青伤,利用 ? 來替換真正的值潦牛。


orWhere 語句

orWhere 函數(shù)只是在 where 函數(shù)的基礎上固定了最后一個參數(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ū)別是對 value 的不同處理绒瘦,whereBasic 實際上是將其看作值,需要用 ? 來替換扣癣,參數(shù)加載到 binding 中去的惰帽。而 whereColumn 是將 second 當做列名來處理,是需要經(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ù)

當 whereIn 第二個參數(shù)是 queryBuild 時莱衩,就會調(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;

}

可以看出爵嗅,這個函數(shù)添加了一個類型為 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ù)

當 whereIn 第二個參數(shù)是閉包函數(shù)的時候操骡,就會調(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 對象之外赚窃,whereInSub 函數(shù)與 whereInExistingQuery 函數(shù)一致。

In / NotIn 類型 where

如果參數(shù)傳遞了數(shù)組岔激,那么就會創(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 對象辱匿,所以直接用 ? 來代替參數(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

關于時間的查詢條件都由函數(shù) addDateBasedWhere 負責創(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ù)據(jù)庫的 date昨忆、month、day杉允、year邑贴、time 函數(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

版權聲明:著作權歸作者所有席里。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權,非商業(yè)轉(zhuǎn)載請保留以上作者信息和原文鏈接拢驾。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奖磁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子繁疤,更是在濱河造成了極大的恐慌咖为,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稠腊,死亡現(xiàn)場離奇詭異躁染,居然都是意外死亡,警方通過查閱死者的電腦和手機麻养,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門褐啡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鳖昌,你說我怎么就攤上這事备畦。” “怎么了许昨?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵懂盐,是天一觀的道長。 經(jīng)常有香客問我糕档,道長莉恼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任速那,我火速辦了婚禮俐银,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘端仰。我一直安慰自己捶惜,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布荔烧。 她就那樣靜靜地躺著吱七,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹤竭。 梳的紋絲不亂的頭發(fā)上踊餐,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音臀稚,去河邊找鬼吝岭。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的苍碟。 我是一名探鬼主播酒觅,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼微峰!你這毒婦竟也來了舷丹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蜓肆,失蹤者是張志新(化名)和其女友劉穎颜凯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仗扬,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡症概,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了早芭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彼城。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖退个,靈堂內(nèi)的尸體忽然破棺而出募壕,到底是詐尸還是另有隱情,我是刑警寧澤语盈,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布舱馅,位于F島的核電站,受9級特大地震影響刀荒,放射性物質(zhì)發(fā)生泄漏代嗤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一缠借、第九天 我趴在偏房一處隱蔽的房頂上張望干毅。 院中可真熱鬧,春花似錦泼返、人聲如沸硝逢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垫毙,卻和暖如春霹疫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背综芥。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工丽蝎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓屠阻,卻偏偏與公主長得像红省,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子国觉,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容