前言

在前两个文章中,我们分析了数据库的连接启动与数据库底层 CRUD 的原理,底层数据库服务支持原生 sql 的运行。本文以 mysql 为例,向大家讲述支持 Fluent 的查询构造器 query 与语法编译器 grammer 的原理。

DB::table 与 查询构造器

若是不想使用原生的 sql 语句,我们可以使用 DB::table 语句,该语句会返回一个 query 对象:

  1. public function table($table)
  2. {
  3. return $this->query()->from($table);
  4. }
  5. public function query()
  6. {
  7. return new QueryBuilder(
  8. $this, $this->getQueryGrammar(), $this->getPostProcessor()
  9. );
  10. }

我们可以看到,query 会有两个成员,queryGrammarpostProcessorqueryGrammar 负责对 QueryBuilcder 的结果进行 sql 语言的转化,postProcessor 负责查询结果的后处理。

之所以 laravel 推荐我们使用查询构造器,而不是原生的 sql,原因在于可以避免 sql 注入漏洞。当然,我们也可以在使用 DB::select() 函数中手动写 bindings 的值,但是这样的话,我们写 sql 的语句是就必须是这样:

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

必然会带来很多不便。

有了查询构造器,我们就可以写出 fluent 类型的语句:

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

是不是很方便?

CRUD 与语法编译器

相应于 connection 对象的 CRUD,语法编译器有 compileInsertcompileSelectcompileUpdatecompileDelete。其中最重要的是 compileSelect,因为它不仅负责了 select 语句的语法编译,还负责聚合语句 aggregatefrom 语句、join 连接语句、wheres 条件语句、groups 分组语句、havings 条件语句、orders 排序语句、limit 语句、offset 语句、unions 联合语句、lock 语句:

  1. protected $selectComponents = [
  2. 'aggregate',
  3. 'columns',
  4. 'from',
  5. 'joins',
  6. 'wheres',
  7. 'groups',
  8. 'havings',
  9. 'orders',
  10. 'limit',
  11. 'offset',
  12. 'unions',
  13. 'lock',
  14. ];
  15. public function compileSelect(Builder $query)
  16. {
  17. $original = $query->columns;
  18. if (is_null($query->columns)) {
  19. $query->columns = ['*'];
  20. }
  21. $sql = trim($this->concatenate(
  22. $this->compileComponents($query))
  23. );
  24. $query->columns = $original;
  25. return $sql;
  26. }
  27. protected function compileComponents(Builder $query)
  28. {
  29. $sql = [];
  30. foreach ($this->selectComponents as $component) {
  31. if (! is_null($query->$component)) {
  32. $method = 'compile'.ucfirst($component);
  33. $sql[$component] = $this->$method($query, $query->$component);
  34. }
  35. }
  36. return $sql;
  37. }

可以看出来,语法编译器会将上述所有的语句放入 $sql[] 成员中,然后通过 concatenate 函数组装成 sql 语句:

  1. protected function concatenate($segments)
  2. {
  3. return implode(' ', array_filter($segments, function ($value) {
  4. return (string) $value !== '';
  5. }));
  6. }

wrap 函数

若想要了解语法编译器,我们就必须先要了解 grammer 中一个重要的函数 wrap,这个函数专门对表名与列名进行处理,

  1. public function wrap($value, $prefixAlias = false)
  2. {
  3. if ($this->isExpression($value)) {
  4. return $this->getValue($value);
  5. }
  6. if (strpos(strtolower($value), ' as ') !== false) {
  7. return $this->wrapAliasedValue($value, $prefixAlias);
  8. }
  9. return $this->wrapSegments(explode('.', $value));
  10. }

处理的流程:

  • 若是 Expression 对象,利用函数 getValue 直接取出对象值,不对其进行任何处理,用于处理原生 sqlexpression 对象的作用是保护原始参数,避免框架解析的一种方式。也就是说,当我们用了 expression 来包装参数的话,laravel 将不会对其进行任何处理,包括库名解析、表名前缀、别名等。
  • 若表名/列名存在 as,则利用函数 wrapAliasedValue 为表名设置别名。
  • 若表名/列名含有 .,则会被分解为 库名/表名,或者 表名/列名,并调用函数 wrapSegments

wrapAliasedValue 函数

wrapAliasedValue 函数用于处理别名:

  1. protected function wrapAliasedValue($value, $prefixAlias = false)
  2. {
  3. $segments = preg_split('/\s+as\s+/i', $value);
  4. if ($prefixAlias) {
  5. $segments[1] = $this->tablePrefix.$segments[1];
  6. }
  7. return $this->wrap(
  8. $segments[0]).' as '.$this->wrapValue($segments[1]
  9. );
  10. }

可以看到,首先程序会根据 as 将字符串分为两部分,as 前的部分递归调用 wrap 函数,as 后的部分调用 wrapValue 函数.

wrapValue 函数

wrapValue 函数用来处理添加符号 ",例如 table,会被这个函数变为 "table"。需要注意的是 table1"table2 这种情况,假如我们的数据库中存在一个表,名字就叫做: table1"table2,我们在数据库查询的时候,必须将表名转化为 "table1""table2",只有这样,数据库才会有效地转化表名为 "table1"table2",否则数据库就会报告错误:找不到表。

  1. protected function wrapValue($value)
  2. {
  3. if ($value !== '*') {
  4. return '"'.str_replace('"', '""', $value).'"';
  5. }
  6. return $value;
  7. }

wrapSegments 函数

wrapSegments 函数会判断当前参数,如果是 table.column,会将前一部分 table 调用 wrapTable, column 调用 wrapValue,最后生成 “table”."column"

  1. protected function wrapSegments($segments)
  2. {
  3. return collect($segments)->map(function ($segment, $key) use ($segments) {
  4. return $key == 0 && count($segments) > 1
  5. ? $this->wrapTable($segment)
  6. : $this->wrapValue($segment);
  7. })->implode('.');
  8. }

wrapTable 函数

wrapTable 函数用于为数据表添加表前缀:

  1. public function wrapTable($table)
  2. {
  3. if (! $this->isExpression($table)) {
  4. return $this->wrap($this->tablePrefix.$table, true);
  5. }
  6. return $this->getValue($table);
  7. }

wrap 整体流程图如下:

Markdown

from 语句

我们看到 DB::table 实际上是调用了查询构造器的 from 函数。接下来我们就看看,当我们写下了

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

时发生了什么。

  1. public function from($table)
  2. {
  3. $this->from = $table;
  4. return $this;
  5. }

我们看到,from 函数极其简单,我们接下来看 get

  1. public function get($columns = ['*'])
  2. {
  3. $original = $this->columns;
  4. if (is_null($original)) {
  5. $this->columns = $columns;
  6. }
  7. $results = $this->processor->processSelect($this, $this->runSelect());
  8. $this->columns = $original;
  9. return collect($results);
  10. }

laravel 的查询构造器是懒加载的,只有调用了 get 函数才会真正的调用语法编译器,采用调用底层 connection 对象进行数据库查询:

  1. protected function runSelect()
  2. {
  3. return $this->connection->select(
  4. $this->toSql(), $this->getBindings(), ! $this->useWritePdo
  5. );
  6. }
  7. public function toSql()
  8. {
  9. return $this->grammar->compileSelect($this);
  10. }

compileFrom 函数

首先我们先看看流程图:

Markdown

语法编译器 grammer 对于 from 语句的处理由函数 compileFrom 负责:

  1. protected function compileFrom(Builder $query, $table)
  2. {
  3. return 'from '.$this->wrapTable($table);
  4. }

从流程图可以看出,具体流程与 wrap 类似。

我们调用 from 时,可以传递两种参数,一种是字符串,另一种是 expression 对象:

  1. DB::table('table');
  2. DB::table(new Expression('table'));
  • 传递 expression 对象

当我们传递 expression 对象的时候,grammer 就会调用 getValue 取出原生 sql 语句。

  • 传递字符串

当我们向 from 传递普通的字符串时,laravel 就会对字符串调用 wrap 函数进行处理,处理流程上一个小节已经说明:

  • 为表名加上前缀 $this->tablePrefix
  • 若字符串存在 as,则为表名设置别名。
  • 若字符串含有 .,则会被分解为 库名表名,并进行分别调用 wrapTable 函数与 wrapValue 进行处理。
  • 为表名前后添加 ",例如 t1.t2 会被转化为 "t1"."t2"(不同的数据库添加的字符不同,mysql 就不是 "

laravelfrom 处理流程存在一些问题,表名前缀设置功能与数据库名功能公用存在问题,相关 issue 地址是:[Bug] Table prefix added to database name when using database.table,有任何兴趣的同学可以在这个 issue 里面讨论,或者直接向作者提 PR。若作者对此部分有任何修改,我会同步修改这篇文章。

Select 语句

本小节会介绍 select 语句:

  1. public function select($columns = ['*'])
  2. {
  3. $this->columns = is_array($columns) ? $columns : func_get_args();
  4. return $this;
  5. }

queryBuilderselect 语句很简单,我们不多讨论.

selectRaw

  1. public function selectRaw($expression, array $bindings = [])
  2. {
  3. $this->addSelect(new Expression($expression));
  4. if ($bindings) {
  5. $this->addBinding($bindings, 'select');
  6. }
  7. return $this;
  8. }
  9. public function addSelect($column)
  10. {
  11. $column = is_array($column) ? $column : func_get_args();
  12. $this->columns = array_merge((array) $this->columns, $column);
  13. return $this;
  14. }

可以看到, selectRaw 就是将 Expression 对象赋值到 columns 中,我们在前面说到,框架不会对 Expression 进行任何处理(更准确的说是 wrap 函数),这样就保证了原生语句的执行。

我们接着看 grammer .

compileColumns 函数

  1. protected function compileColumns(Builder $query, $columns)
  2. {
  3. if (! is_null($query->aggregate)) {
  4. return;
  5. }
  6. $select = $query->distinct ? 'select distinct ' : 'select ';
  7. return $select.$this->columnize($columns);
  8. }
  9. public function columnize(array $columns)
  10. {
  11. return implode(', ', array_map([$this, 'wrap'], $columns));
  12. }

可以看到,grammerselect 的语法编译调用 wrap 函数对每个 select 的字段进行处理,处理过程在上面详解过,在此不再赘述。

selectSub 语句

所谓的 select 子查询,就是查询的字段来源于其他数据表。对于这种查询,可以分成两部来理解,首先忽略整个select子查询,查出第一个表中的数据,然后根据第一个表的数据执行子查询,

laravelselectSub 支持闭包函数、queryBuild 对象或者原生 sql 语句,以下是单元测试样例:

  1. $query = DB::table('one')->select(['foo', 'bar'])->where('key', '=', 'val');
  2. $query->selectSub(function ($query) {
  3. $query->from('two')->select('baz')->where('subkey', '=', 'subval');
  4. }, 'sub');

另一种写法:

  1. $query = DB::table('one')->select(['foo', 'bar'])->where('key', '=', 'val');
  2. $query_sub = DB::table('one')->select('baz')->where('subkey', '=', 'subval');
  3. $query->selectSub($query_sub, 'sub');

生成的 sql

  1. select "foo", "bar", (select "baz" from "two" where "subkey" = 'subval') as "sub" from "one" where "key" = 'val'

selectSub 语句的实现比较简单:

  1. public function selectSub($query, $as)
  2. {
  3. if ($query instanceof Closure) {
  4. $callback = $query;
  5. $callback($query = $this->forSubQuery());
  6. }
  7. list($query, $bindings) = $this->parseSubSelect($query);
  8. return $this->selectRaw(
  9. '('.$query.') as '.$this->grammar->wrap($as), $bindings
  10. );
  11. }
  12. protected function parseSubSelect($query)
  13. {
  14. if ($query instanceof self) {
  15. $query->columns = [$query->columns[0]];
  16. return [$query->toSql(), $query->getBindings()];
  17. } elseif (is_string($query)) {
  18. return [$query, []];
  19. } else {
  20. throw new InvalidArgumentException;
  21. }
  22. }

可以看到,如果 selectSub 的参数是闭包函数,那么就会先执行闭包函数,闭包函数将会为 query 根据查询语句更新对象。

parseSubSelect 函数为子查询解析 sql 语句与 binding 变量。

where 语句总结

laravel 文档中,queryBuild 的用法很详尽,但是为了更好的理解源码,我们在这里再次大概的总结一下:

基础用法

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

这两个是等价的写法。

where 数组

  1. $users = DB::table('users')->where(
  2. ['status' => 1, 'subscribed' => 1],
  3. )->get();
  4. $users = DB::table('users')->where([
  5. ['status', '1'],
  6. ['subscribed', '1'],
  7. ])->get();
  8. $users = DB::table('users')->where([
  9. ['status', '1'],
  10. ['subscribed', '<>', '1'],
  11. ])->get();

where 查询组

  1. DB::table('users')
  2. ->where('name', '=', 'John')
  3. ->orWhere(function ($query) {
  4. $query->where('votes', '>', 100)
  5. ->where('title', '<>', 'Admin');
  6. })
  7. ->get();

这一句的 sql 语句是

  1. select * from users where name = 'John' or (votes > 100 and title <> 'Admin')

where 子查询

  1. public function testFullSubSelects()
  2. {
  3. $builder = $this->getBuilder();
  4. DB::table('users')
  5. ->Where('id', '=', function ($q) {
  6. $q->select(new Raw('max(id)'))->from('users')->where('email', '=', 'bar');
  7. });
  8. }

这一句的 sql 语句是

  1. select * from "users" where "email" = foo or "id" = (select max(id) from "users" where "email" = bar`

orWhere

  1. $users = DB::table('users')
  2. ->where('votes', '>', 100)
  3. ->orWhere('name', 'John')
  4. ->get();

whereDate / whereMonth / whereDay / whereYear / whereTime

  1. $users = DB::table('users')
  2. ->whereDate('created_at', '2016-12-31')
  3. ->get();
  4. $users = DB::table('users')
  5. ->whereMonth('created_at', '12')
  6. ->get();
  7. $users = DB::table('users')
  8. ->whereDay('created_at', '31')
  9. ->get();
  10. $users = DB::table('users')
  11. ->whereYear('created_at', '2016')
  12. ->get();
  13. $users = DB::table('users')
  14. ->whereTime('created_at', '>=', '22:00')
  15. ->get();

whereBetween / whereNotBetween

  1. $users = DB::table('users')
  2. ->whereBetween('votes', [1, 100])->get();
  3. $users = DB::table('users')
  4. ->whereNotBetween('votes', [1, 100])
  5. ->get();

whereRaw / orWhereRaw

  1. $users = DB::table('users')
  2. ->whereRaw('id = ? or email = ?', [1, 'foo'])
  3. ->get();
  4. $users = DB::table('users')
  5. ->orWhereRaw('id = ? or email = ?', [1, 'foo'])
  6. ->get();

whereIn / whereNotIn / orWhereIn / orWhereNotIn

  1. $users = DB::table('users')
  2. ->whereIn('id', [1, 2, 3])
  3. ->get();
  4. $users = DB::table('users')
  5. ->whereNotIn('id', [1, 2, 3])
  6. ->get();
  7. $users = DB::table('users')
  8. ->whereIn('id', function ($q) {
  9. $q->select('id')->from('users')->where('age', '>', 25)->take(3);
  10. });
  11. $users = DB::table('users')
  12. ->whereNotIn('id', function ($q) {
  13. $q->select('id')->from('users')->where('age', '>', 25)->take(3);
  14. });
  15. $query = DB::table('users')->select('id')->where('age', '>', 25)->take(3);
  16. $users = DB::table('users')->whereIn('id', $query);
  17. $users = DB::table('users')->whereNotIn('id', $query);

有意思的是,当我们在 whereIn / whereNotIn / orWhereIn / orWhereNotIn 中传入空数组的时候:

  1. $users = DB::table('users')
  2. ->whereIn('id', [])
  3. ->get();
  4. $users = DB::table('users')
  5. ->orWhereIn('id', [])
  6. ->get();

这个时候,框架自动会生成如下的 sql

  1. select * from "users" where 0 = 1;
  2. select * from "users" where "id" = ? or 0 = 1

whereColumn

  1. $users = DB::table('users')
  2. ->whereColumn('first_name', 'last_name')
  3. ->get();
  4. $users = DB::table('users')
  5. ->whereColumn('updated_at', '>', 'created_at')
  6. ->get();
  7. $users = DB::table('users')
  8. ->whereColumn([
  9. ['first_name', '=', 'last_name'],
  10. ['updated_at', '>', 'created_at']
  11. ])->get();

whereNull / whereNotNull / orWhereNull / orWhereNotNull

  1. $users = DB::table('users')
  2. ->whereNull('updated_at')
  3. ->get();
  4. $users = DB::table('users')
  5. ->whereNotNull('updated_at')
  6. ->get();
  7. $users = DB::table('users')
  8. ->orWhereNull('updated_at')
  9. ->get();
  10. $users = DB::table('users')
  11. ->orWhereNotNull('updated_at')
  12. ->get();

whereExists / whereNotExists / orWhereExists / orWhereNotExists

  1. DB::table('users')
  2. ->whereExists(function ($query) {
  3. $query->select(DB::raw(1))
  4. ->from('orders')
  5. ->whereRaw('orders.user_id = users.id');
  6. })
  7. ->get();
  8. // select * from users where exists ( select 1 from orders where orders.user_id = users.id)
  9. DB::table('users')
  10. ->whereNotExists(function ($query) {
  11. $query->select(DB::raw(1))
  12. ->from('orders')
  13. ->whereRaw('orders.user_id = users.id');
  14. })
  15. ->get();
  16. // select * from users where not exists ( select 1 from orders where orders.user_id = users.id)
  17. DB::table('users')
  18. ->orWhereExists(function ($query) {
  19. $query->select(DB::raw(1))
  20. ->from('orders')
  21. ->whereRaw('orders.user_id = users.id');
  22. })
  23. ->get();
  24. // select * from users or exists ( select 1 from orders where orders.user_id = users.id)
  25. DB::table('users')
  26. ->orWhereNotExists(function ($query) {
  27. $query->select(DB::raw(1))
  28. ->from('orders')
  29. ->whereRaw('orders.user_id = users.id');
  30. })
  31. ->get();
  32. // select * from users or not exists ( select 1 from orders where orders.user_id = users.id)

where 函数

我们首先先看看源码:

  1. public function where($column, $operator = null, $value = null, $boolean = 'and')
  2. {
  3. if (is_array($column)) {
  4. return $this->addArrayOfWheres($column, $boolean);
  5. }
  6. list($value, $operator) = $this->prepareValueAndOperator(
  7. $value, $operator, func_num_args() == 2
  8. );
  9. if ($column instanceof Closure) {
  10. return $this->whereNested($column, $boolean);
  11. }
  12. if ($this->invalidOperator($operator)) {
  13. list($value, $operator) = [$operator, '='];
  14. }
  15. if ($value instanceof Closure) {
  16. return $this->whereSub($column, $operator, $value, $boolean);
  17. }
  18. if (is_null($value)) {
  19. return $this->whereNull($column, $boolean, $operator !== '=');
  20. }
  21. if (Str::contains($column, '->') && is_bool($value)) {
  22. $value = new Expression($value ? 'true' : 'false');
  23. }
  24. $type = 'Basic';
  25. $this->wheres[] = compact(
  26. 'type', 'column', 'operator', 'value', 'boolean'
  27. );
  28. if (! $value instanceof Expression) {
  29. $this->addBinding($value, 'where');
  30. }
  31. return $this;
  32. }

可以看到,为了支持框架的多种 where 形式,where 的代码中写了很多的条件语句。我们接下来一个个分析。

grammer——compileWheres 函数

在此之前,我们先看看语法编译器对 where 查询的处理:

  1. protected function compileWheres(Builder $query)
  2. {
  3. if (is_null($query->wheres)) {
  4. return '';
  5. }
  6. if (count($sql = $this->compileWheresToArray($query)) > 0) {
  7. return $this->concatenateWhereClauses($query, $sql);
  8. }
  9. return '';
  10. }

compileWheres 函数负责所有 where 查询条件的语法编译工作,compileWheresToArray 函数负责循环编译查询条件,concatenateWhereClauses 函数负责将多个查询条件合并。

  1. protected function compileWheresToArray($query)
  2. {
  3. return collect($query->wheres)->map(function ($where) use ($query) {
  4. return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where);
  5. })->all();
  6. }
  7. protected function concatenateWhereClauses($query, $sql)
  8. {
  9. $conjunction = $query instanceof JoinClause ? 'on' : 'where';
  10. return $conjunction.' '.$this->removeLeadingBoolean(implode(' ', $sql));
  11. }

compileWheresToArray 函数负责把 $query->wheres 中多个 where 条件循环起来:

  • $where['boolean'] 是多个查询条件的连接,and 或者 or,一般 where 条件默认为 and,各种 orWhere 的连接是 or
  • where{$where['type']} 是查询的类型,laravel 把查询条件分为以下几类:baserawinnotIninSubnotInSubnullnotNullbetweencolumnnestedsubexistnotExist。每种类型的查询条件都有对应的 grammer 方法

concatenateWhereClauses 函数负责连接所有的搜索条件,由于 join 的连接条件也会调用 compileWheres 函数,所以会有判断是否是真正的 where 查询,

where 数组

如果 column 是数组的话,就会调用:

  1. protected function addArrayOfWheres($column, $boolean, $method = 'where')
  2. {
  3. return $this->whereNested(function ($query) use ($column, $method, $boolean) {
  4. foreach ($column as $key => $value) {
  5. if (is_numeric($key) && is_array($value)) {
  6. $query->{$method}(...array_values($value));
  7. } else {
  8. $query->$method($key, '=', $value, $boolean);
  9. }
  10. }
  11. }, $boolean);
  12. }

可以看到,数组分为两类,一种是列名为 key,例如 ['foo' => 1, 'bar' => 2],这个时候就是调用 query->where('foo', '=', '1', ‘and’)。还有一种是 [['foo','1'],['bar','2']],这个时候就会调用 $query->where(['foo','1'])

  1. public function whereNested(Closure $callback, $boolean = 'and')
  2. {
  3. call_user_func($callback, $query = $this->forNestedWhere());
  4. return $this->addNestedWhereQuery($query, $boolean);
  5. }
  6. public function addNestedWhereQuery($query, $boolean = 'and')
  7. {
  8. if (count($query->wheres)) {
  9. $type = 'Nested';
  10. $this->wheres[] = compact('type', 'query', 'boolean');
  11. $this->addBinding($query->getBindings(), 'where');
  12. }
  13. return $this;
  14. }

grammer——whereNested

语法编译器中负责查询组的函数是 whereNested,它会取出 where 中的 query,递归调用 compileWheres 函数

  1. protected function whereNested(Builder $query, $where)
  2. {
  3. $offset = $query instanceof JoinClause ? 3 : 6;
  4. return '('.substr($this->compileWheres($where['query']), $offset).')';
  5. }

由于 compileWheres 会返回 where ... 或者 on ... 等开头的 sql 语句,所以我们需要把返回结果截取前3个字符或6个字符。

where 查询组

若查询条件是一个闭包函数,也就是第一个参数 column 是个闭包函数,那么就要调用 whereNested 函数,过程和上述过程一致。

whereSub 子查询

如果第二个参数或者第三个参数是一个闭包函数的话,就是 where 子查询语句,这时需要调用 whereSub 函数:

  1. protected function whereSub($column, $operator, Closure $callback, $boolean)
  2. {
  3. $type = 'Sub';
  4. call_user_func($callback, $query = $this->forSubQuery());
  5. $this->wheres[] = compact(
  6. 'type', 'column', 'operator', 'query', 'boolean'
  7. );
  8. $this->addBinding($query->getBindings(), 'where');
  9. return $this;
  10. }

grammer——whereSub

grammer 中负责子查询的是 whereSub 函数:

  1. protected function whereSub(Builder $query, $where)
  2. {
  3. $select = $this->compileSelect($where['query']);
  4. return $this->wrap($where['column']).' '.$where['operator']." ($select)";
  5. }

因为子查询中可以存在 selectwherejoin 等一切 sql 语句,所以递归的是 compileSelect 这个大的函数,而不是仅仅 compileWheres

whereNull 语句

whereNull 函数也很简单:

  1. public function whereNull($column, $boolean = 'and', $not = false)
  2. {
  3. $type = $not ? 'NotNull' : 'Null';
  4. $this->wheres[] = compact('type', 'column', 'boolean');
  5. return $this;
  6. }

grammer——whereNull 函数

  1. protected function whereNull(Builder $query, $where)
  2. {
  3. return $this->wrap($where['column']).' is null';
  4. }

whereBasic 语句

如果上述情况都不符合,那么就是最基础的 where 语句,类型是 basic.

  1. $type = 'Basic';
  2. $this->wheres[] = compact(
  3. 'type', 'column', 'operator', 'value', 'boolean'
  4. );
  5. if (! $value instanceof Expression) {
  6. $this->addBinding($value, 'where');
  7. }

grammer——whereBasic 函数

grammer 中最基础的 where 语句由 wherebasic 函数负责:

  1. protected function whereBasic(Builder $query, $where)
  2. {
  3. $value = $this->parameter($where['value']);
  4. return $this->wrap($where['column']).' '.$where['operator'].' '.$value;
  5. }
  6. public function parameter($value)
  7. {
  8. return $this->isExpression($value) ? $this->getValue($value) : '?';
  9. }

wherebasic 函数对参数进行了替换,利用 ? 来替换真正的值。

orWhere 语句

orWhere 函数只是在 where 函数的基础上固定了最后一个参数:

  1. public function orWhere($column, $operator = null, $value = null)
  2. {
  3. return $this->where($column, $operator, $value, 'or');
  4. }

whereColumn 语句

whereColumn 函数是简化版的 where 函数,只是 where 类型不是 basic,而是 column

  1. public function whereColumn($first, $operator = null, $second = null, $boolean = 'and')
  2. {
  3. if (is_array($first)) {
  4. return $this->addArrayOfWheres($first, $boolean, 'whereColumn');
  5. }
  6. if ($this->invalidOperator($operator)) {
  7. list($second, $operator) = [$operator, '='];
  8. }
  9. $type = 'Column';
  10. $this->wheres[] = compact(
  11. 'type', 'first', 'operator', 'second', 'boolean'
  12. );
  13. return $this;
  14. }

grammer——whereColumn

可以看到 whereColumnwhereBasic 的区别是对 value 的不同处理,whereBasic 实际上是将其看作值,需要用 ? 来替换,参数加载到 binding 中去的。而 whereColumn 是将 second 当做列名来处理,是需要经过表名、别名等处理的:

  1. protected function whereColumn(Builder $query, $where)
  2. {
  3. return $this->wrap($where['first']).' '.$where['operator'].' '.$this->wrap($where['second']);
  4. }

whereIn 语句

  1. public function whereIn($column, $values, $boolean = 'and', $not = false)
  2. {
  3. $type = $not ? 'NotIn' : 'In';
  4. if ($values instanceof EloquentBuilder) {
  5. $values = $values->getQuery();
  6. }
  7. if ($values instanceof self) {
  8. return $this->whereInExistingQuery(
  9. $column, $values, $boolean, $not
  10. );
  11. }
  12. if ($values instanceof Closure) {
  13. return $this->whereInSub($column, $values, $boolean, $not);
  14. }
  15. if ($values instanceof Arrayable) {
  16. $values = $values->toArray();
  17. }
  18. $this->wheres[] = compact('type', 'column', 'values', 'boolean');
  19. foreach ($values as $value) {
  20. if (! $value instanceof Expression) {
  21. $this->addBinding($value, 'where');
  22. }
  23. }
  24. return $this;
  25. }

可以看出来,whereIn 支持四种参数: EloquentBuilderqueryBuilderClosureArrayable

whereInExistingQuery 函数

whereIn 第二个参数是 queryBuild 时,就会调用 whereInExistingQuery 函数:

  1. protected function whereInExistingQuery($column, $query, $boolean, $not)
  2. {
  3. $type = $not ? 'NotInSub' : 'InSub';
  4. $this->wheres[] = compact('type', 'column', 'query', 'boolean');
  5. $this->addBinding($query->getBindings(), 'where');
  6. return $this;
  7. }

可以看出,这个函数添加了一个类型为 InSubNotInSub 类型的 where,我们接着在语法编译器来看:

grammer——whereInSub / whereNotInSub

whereInSub / whereNotInSubwhereSub 类似,只是 operator 被固定成 in / not in 而已:

  1. protected function whereInSub(Builder $query, $where)
  2. {
  3. return $this->wrap($where['column']).' in ('.$this->compileSelect($where['query']).')';
  4. }
  5. protected function whereNotInSub(Builder $query, $where)
  6. {
  7. return $this->wrap($where['column']).' not in ('.$this->compileSelect($where['query']).')';
  8. }

whereInSub 函数

whereIn 第二个参数是闭包函数的时候,就会调用 whereInSub 函数:

  1. protected function whereInSub($column, Closure $callback, $boolean, $not)
  2. {
  3. $type = $not ? 'NotInSub' : 'InSub';
  4. call_user_func($callback, $query = $this->forSubQuery());
  5. $this->wheres[] = compact('type', 'column', 'query', 'boolean');
  6. $this->addBinding($query->getBindings(), 'where');
  7. return $this;
  8. }

可以看出来,除了闭包函数需要执行获得 query 对象之外,whereInSub 函数与 whereInExistingQuery 函数一致。

In / NotIn 类型 where

如果参数传递了数组,那么就会创建 In 或者 NotIn 类型的 where:

  1. $this->wheres[] = compact('type', 'column', 'values', 'boolean');
  2. foreach ($values as $value) {
  3. if (! $value instanceof Expression) {
  4. $this->addBinding($value, 'where');
  5. }
  6. }

grammer——whereIn / whereNotIn

whereIn 或者 whereNotInwhereBasic 函数基本一致,只是参数值需要循环调用 parameter 函数:

  1. protected function whereIn(Builder $query, $where)
  2. {
  3. if (! empty($where['values'])) {
  4. return $this->wrap($where['column']).' in ('.$this->parameterize($where['values']).')';
  5. }
  6. return '0 = 1';
  7. }
  8. protected function whereNotIn(Builder $query, $where)
  9. {
  10. if (! empty($where['values'])) {
  11. return $this->wrap($where['column']).' not in ('.$this->parameterize($where['values']).')';
  12. }
  13. return '1 = 1';
  14. }
  15. public function parameterize(array $values)
  16. {
  17. return implode(', ', array_map([$this, 'parameter'], $values));
  18. }

whereBetween 语句

类似的 whereBetween 创建了新的 between 类型的 where

  1. public function whereBetween($column, array $values, $boolean = 'and', $not = false)
  2. {
  3. $type = 'between';
  4. $this->wheres[] = compact('column', 'type', 'boolean', 'not');
  5. $this->addBinding($values, 'where');
  6. return $this;
  7. }

grammer——whereBetween

有意思的是,whereBetween 不支持原生的参数值,也就是不支持 expression 对象,所以直接用 ? 来代替参数值:

  1. protected function whereBetween(Builder $query, $where)
  2. {
  3. $between = $where['not'] ? 'not between' : 'between';
  4. return $this->wrap($where['column']).' '.$between.' ? and ?';
  5. }

whereExist 语句

whereExist 只支持闭包函数作为子查询语句,和之前一样,创建了 Exist 或者 NotExist 类型的 where

  1. public function whereExists(Closure $callback, $boolean = 'and', $not = false)
  2. {
  3. $query = $this->forSubQuery();
  4. call_user_func($callback, $query);
  5. return $this->addWhereExistsQuery($query, $boolean, $not);
  6. }
  7. public function addWhereExistsQuery(Builder $query, $boolean = 'and', $not = false)
  8. {
  9. $type = $not ? 'NotExists' : 'Exists';
  10. $this->wheres[] = compact('type', 'operator', 'query', 'boolean');
  11. $this->addBinding($query->getBindings(), 'where');
  12. return $this;
  13. }

grammer——whereExists / whereNotExists

可以看到,这部分的语法编译器仍然和 whereSub 非常类似:

  1. protected function whereExists(Builder $query, $where)
  2. {
  3. return 'exists ('.$this->compileSelect($where['query']).')';
  4. }
  5. protected function whereNotExists(Builder $query, $where)
  6. {
  7. return 'not exists ('.$this->compileSelect($where['query']).')';
  8. }

whereDate / whereMonth / whereDay / whereYear / whereTime

关于时间的查询条件都由函数 addDateBasedWhere 负责创建新的类型的 where,由于这些函数大致相同,我这里只贴一种:

  1. protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
  2. {
  3. $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
  4. $this->addBinding($value, 'where');
  5. return $this;
  6. }
  7. public function whereDate($column, $operator, $value = null, $boolean = 'and')
  8. {
  9. list($value, $operator) = $this->prepareValueAndOperator(
  10. $value, $operator, func_num_args() == 2
  11. );
  12. return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
  13. }
  14. ...

grammer——dateBasedWhere

时间查询条件都是利用数据库的 datemonthdayyeartime 函数实现的:

  1. protected function whereTime(Builder $query, $where)
  2. {
  3. return $this->dateBasedWhere('time', $query, $where);
  4. }
  5. protected function dateBasedWhere($type, Builder $query, $where)
  6. {
  7. $value = $this->parameter($where['value']);
  8. return $type.'('.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
  9. }

whereRaw 语句

原生的 sql 语句及其简单:

  1. public function whereRaw($sql, $bindings = [], $boolean = 'and')
  2. {
  3. $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean];
  4. $this->addBinding((array) $bindings, 'where');
  5. return $this;
  6. }

语法编译器工作:

  1. protected function whereRaw(Builder $query, $where)
  2. {
  3. return $where['sql'];
  4. }