Laravel Database——查询构造器与语法编译器源码分析 (上)

栏目: IT技术 · 发布时间: 5年前

内容简介:Laravel Database——查询构造器与语法编译器源码分析 (上)

前言

本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
在前两个文章中,我们分析了数据库的连接启动与数据库底层 CRUD 的原理,底层数据库服务支持原生 sql 的运行。本文以 mysql 为例,向大家讲述支持 Fluent 的查询构造器 query 与语法编译器 grammer 的原理。

 

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 会有两个成员,queryGrammarpostProcessorqueryGrammar 负责对 QueryBuilcder 的结果进行 sql 语言的转化,postProcessor 负责查询结果的后处理。

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

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

必然会带来很多不便。

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

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

是不是很方便?

 

CRUD 与语法编译器

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

protected function concatenate($segments)
{
    return implode(' ', array_filter($segments, function ($value) {
        return (string) $value !== '';
    }));
}

wrap 函数

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

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

wrapAliasedValue 函数

wrapAliasedValue 函数用于处理别名:

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

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

wrapValue 函数

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

protected function wrapValue($value)
{
    if ($value !== '*') {
        return '"'.str_replace('"', '""', $value).'"';
    }

    return $value;
}

wrapSegments 函数

wrapSegments 函数会判断当前参数,如果是 table.column,会将前一部分 table 调用 wrapTable, column 调用 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 函数

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

public function wrapTable($table)
{
    if (! $this->isExpression($table)) {
        return $this->wrap($this->tablePrefix.$table, true);
    }

    return $this->getValue($table);
}

wrap 整体流程图如下:

Laravel Database——查询构造器与语法编译器源码分析 (上)

 

from 语句

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

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

时发生了什么。

public function from($table)
{
    $this->from = $table;

    return $this;
}

我们看到,from 函数极其简单,我们接下来看 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 的查询构造器是懒加载的,只有调用了 get 函数才会真正的调用语法编译器,采用调用底层 connection 对象进行数据库查询:

protected function runSelect()
{
    return $this->connection->select(
        $this->toSql(), $this->getBindings(), ! $this->useWritePdo
    );
}

public function toSql()
{
    return $this->grammar->compileSelect($this);
}

compileFrom 函数

首先我们先看看流程图:

Laravel Database——查询构造器与语法编译器源码分析 (上)

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

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

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

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

DB::table('table');
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 语句:

public function select($columns = ['*'])
{
    $this->columns = is_array($columns) ? $columns : func_get_args();

    return $this;
}

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

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 函数),这样就保证了原生语句的执行。

我们接着看 grammer .

compileColumns 函数

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

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

 

selectSub 语句

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

laravelselectSub 支持闭包函数、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 语句的实现比较简单:

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 的参数是闭包函数,那么就会先执行闭包函数,闭包函数将会为 query 根据查询语句更新对象。

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

 

where 语句总结

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

基础用法

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

这两个是等价的写法。

where 数组

$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 中传入空数组的时候:

$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 函数

我们首先先看看源码:

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 函数

在此之前,我们先看看语法编译器对 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 函数负责所有 where 查询条件的语法编译工作,compileWheresToArray 函数负责循环编译查询条件,concatenateWhereClauses 函数负责将多个查询条件合并。

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 函数负责把 $query->wheres 中多个 where 条件循环起来:

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

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

where 数组

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

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

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

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

protected function whereNested(Builder $query, $where)
{
    $offset = $query instanceof JoinClause ? 3 : 6;

    return '('.substr($this->compileWheres($where['query']), $offset).')';
}

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

where 查询组

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

whereSub 子查询

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

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 函数:

protected function whereSub(Builder $query, $where)
{
    $select = $this->compileSelect($where['query']);

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

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

whereNull 语句

whereNull 函数也很简单:

public function whereNull($column, $boolean = 'and', $not = false)
{
    $type = $not ? 'NotNull' : 'Null';

    $this->wheres[] = compact('type', 'column', 'boolean');

    return $this;
}

grammer——whereNull 函数

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 函数

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

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 函数对参数进行了替换,利用 ? 来替换真正的值。

 

orWhere 语句

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

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

 

whereColumn 语句

whereColumn 函数是简化版的 where 函数,只是 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

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

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 支持四种参数: EloquentBuilderqueryBuilderClosureArrayable

whereInExistingQuery 函数

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

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

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

grammer——whereInSub / whereNotInSub

whereInSub / whereNotInSubwhereSub 类似,只是 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 函数

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

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

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

In / NotIn 类型 where

如果参数传递了数组,那么就会创建 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 或者 whereNotInwhereBasic 函数基本一致,只是参数值需要循环调用 parameter 函数:

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 创建了新的 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 不支持原生的参数值,也就是不支持 expression 对象,所以直接用 ? 来代替参数值:

protected function whereBetween(Builder $query, $where)
{
    $between = $where['not'] ? 'not between' : 'between';

    return $this->wrap($where['column']).' '.$between.' ? and ?';
}

 

whereExist 语句

whereExist 只支持闭包函数作为子查询语句,和之前一样,创建了 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

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

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

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

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'];
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art of Computer Programming, Volumes 1-3 Boxed Set

The Art of Computer Programming, Volumes 1-3 Boxed Set

Donald E. Knuth / Addison-Wesley Professional / 1998-10-15 / USD 199.99

This multivolume work is widely recognized as the definitive description of classical computer science. The first three volumes have for decades been an invaluable resource in programming theory and p......一起来看看 《The Art of Computer Programming, Volumes 1-3 Boxed Set》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试