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