MySQL

ORM学习(四) - 查询构造器

2020-01-28  本文已影响0人  半亩房顶

前言

上一节学习了CRUD 操作源码。下面几节来看下将代码优雅组织起来的查询构造器吧
本文主要内容整理自leoyang的系列文章,详情见引用

查询构造器

DB::table 与 查询构造器

在深入开展查询构造器的开始阶段,我们先通过一条简单的语句来说明下查询构造器的执行过程
DB::table('table')->get();
首先,这个DB就是我们之前说的connection,然后是table函数

    // \Illuminate\Database\Connection::table
    public function table($table, $as = null)
    {
        return $this->query()->from($table, $as);
    }

这个链式操作的第一步table函数

    public function query()
    {
        return new QueryBuilder(
            $this, $this->getQueryGrammar(), $this->getPostProcessor()
        );
    }

然后是query(),这个函数返回了一个Builder(Query/Builder),有三个参数,自身、语法编译器、结果处理器

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

        return $this;
    }

跟在query后面的from在这里比较简单,其他的我们后面会提到,这里只是设定了表明。设定完成之后,正式移交到Builder,开始执行get()

    public function get($columns = ['*'])
    {
        return collect($this->onceWithColumns(Arr::wrap($columns), function () {
            return $this->processor->processSelect($this, $this->runSelect());
        }));
    }

直接使用结果处理函数,嵌套runSelect()

    public function processSelect(Builder $query, $results)
    {
        return $results;
    }

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

runSelect真正的调起connection,发起sql调用,toSql()获取sql语句,getBindings()获取绑定参数
直到toSql(),真正使用语法编译器,生成sql语句。grammar是懒加载的,其中的编译等方法,都是在toSql() 时才会调用。

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

下面开始,详细的看看每个步骤吧

语法编译器

先来说下语法编译器,相对于connection的CRUD,语法编译器中都有相对应的函数执行编译操作
compileSelectcompileInsertcompileDeletecompileUpdate等等。
其中尤其要注意的是compileSelect函数,因为这里面包含了太多的功能,因为你会发现很多compile函数并没有调用,当然不是没有使用,而是被包含在了compileComponents函数中了,如下代码所示:

protected $selectComponents = [
        'aggregate',
        'columns',
        'from',
        'joins',
        'wheres',
        'groups',
        'havings',
        'orders',
        'limit',
        'offset',
        'lock',
    ];

    /**
     * Compile a select query into SQL.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @return string
     */
    public function compileSelect(Builder $query)
    {
        if ($query->unions && $query->aggregate) {
            return $this->compileUnionAggregate($query);
        }

        // If the query does not have any columns set, we'll set the columns to the
        // * character to just get all of the columns from the database. Then we
        // can build the query and concatenate all the pieces together as one.
        $original = $query->columns;

        if (is_null($query->columns)) {
            $query->columns = ['*'];
        }

        // To compile the query, we'll spin through each component of the query and
        // see if that component exists. If it does we'll just call the compiler
        // function for the component which is responsible for making the SQL.
        $sql = trim($this->concatenate(
            $this->compileComponents($query))
        );

        if ($query->unions) {
            $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
        }

        $query->columns = $original;

        return $sql;
    }

    /**
     * Compile the components necessary for a select clause.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @return array
     */
    protected function compileComponents(Builder $query)
    {
        $sql = [];

        foreach ($this->selectComponents as $component) {
            // To compile the query, we'll spin through each component of the query and
            // see if that component exists. If it does we'll just call the compiler
            // function for the component which is responsible for making the SQL.
            if (isset($query->$component) && ! 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));
}

其他还有 wrapAliasedValue,wrapSegments,wrapValue等等,各有各的功能,感兴趣可以去源码看下。

from 语句

下面来说下刚才看到了的from语句,上面我们知道了,执行DB::table()时实际上是执行了from函数,它完成了表名的设定,代码再放一遍:

    // \Illuminate\Database\Connection::table
    public function table($table, $as = null)
    {
        return $this->query()->from($table, $as);
    }

这个本身木有什么了,但是我们主要想说的是grammar 中对应的compileFrom 函数:

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

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

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

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

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

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

这里放出本文原作者发现的一个问题,大家感兴趣的可以看下:laravelfrom 处理流程存在一些问题,表名前缀设置功能与数据库名功能公用存在问题,相关 issue 地址是:[Bug] Table prefix added to database name when using database.table

select 语句

好的,接下来,从select语句开始

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

    return $this;
}

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对象时的处理
select 语句在SQL语句中,其实就是列的选择,也就是from之前的部分。
grammar中对应的处理函数就是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));
}

select 函数中还会有一种比较特殊的,selectSub。laravel 的 selectSub 支持闭包函数、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 部分语句

from语句前面已经说过了,下面来看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;
}

满眼望去一堆堆的条件语句,我们挑选其中最常见的两个,结合grammar代码来看:

where 语句

首先是对wheres的处理,compileWheres 函数负责所有 where 查询条件的语法编译工作,compileWheresToArray 函数负责循环编译查询条件,concatenateWhereClauses 函数负责将多个查询条件合并。
compileWheresToArray 函数负责把 $query->wheres 中多个 where 条件循环起来:

concatenateWhereClauses 函数负责连接所有的搜索条件,由于 join 的连接条件也会调用 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 '';
}
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));
}
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;
}

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

其他的更多where语句分析请看原文

join 语句

join 语句对数据库进行连接操作,join 函数的连接条件可以非常简单:

DB::table('services')->select('*')->join('translations AS t', 't.item_id', '=', 'services.id');

也可以比较复杂:

DB::table('users')->select('*')->join('contacts', function ($j) {
        $j->on('users.id', '=', 'contacts.id')->orOn('users.name', '=', 'contacts.name');
    });
    //select * from "users" inner join "contacts" on "users"."id" = "contacts"."id" or "users"."name" = "contacts"."name"

    $builder = $this->getBuilder();
    DB::table('users')->select('*')->from('users')->joinWhere('contacts', 'col1', function ($j) {
        $j->select('users.col2')->from('users')->where('users.id', '=', 'foo')
    });
    //select * from "users" inner join "contacts" on "col1" = (select "users"."col2" from "users" where "users"."id" = foo)

还可以更加复杂!

DB::table('users')->select('*')->leftJoin('contacts', function ($j) {
        $j->on('users.id', '=', 'contacts.id')->where(function ($j) {
            $j->where('contacts.country', '=', 'US')->orWhere('contacts.is_partner', '=', 1);
        });
    });
    //select * from "users" left join "contacts" on "users"."id" = "contacts"."id" and ("contacts"."country" = 'US' or "contacts"."is_partner" = 1)

    DB::table('users')->select('*')->leftJoin('contacts', function ($j) {
        $j->on('users.id', '=', 'contacts.id')->where('contacts.is_active', '=', 1)->orOn(function ($j) {
            $j->orWhere(function ($j) {
                $j->where('contacts.country', '=', 'UK')->orOn('contacts.type', '=', 'users.type');
            })->where(function ($j) {
                $j->where('contacts.country', '=', 'US')->orWhereNull('contacts.is_partner');
            });
        });
    });
    //select * from "users" left join "contacts" on "users"."id" = "contacts"."id" and "contacts"."is_active" = 1 or (("contacts"."country" = 'UK' or "contacts"."type" = "users"."type") and ("contacts"."country" = 'US' or "contacts"."is_partner" is null))

其实 join 语句与 where 语句非常相似,将 join 语句的连接条件看作 where 的查询条件完全可以,接下来我们看看源码。

public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
{
    $join = new JoinClause($this, $type, $table);

    if ($first instanceof Closure) {
        call_user_func($first, $join);

        $this->joins[] = $join;

        $this->addBinding($join->getBindings(), 'join');
    }

    else {
        $method = $where ? 'where' : 'on';

        $this->joins[] = $join->$method($first, $operator, $second);

        $this->addBinding($join->getBindings(), 'join');
    }

    return $this;
}

可以看到,程序首先新建了一个 JoinClause 类对象,这个类实际上继承 queryBuilder,也就是说 queryBuilder 上的很多方法它都可以直接用,例如 where、whereNull、whereDate 等等。
如果第二个参数是闭包函数的话,就会像查询组一样根据查询条件更新 $join

如果第二个参数是列名,那么就会调用 on 方法或 where 方法。这两个方法的区别是,on 方法只支持 whereColumn 方法和 whereNested,也就是说只能写出 join on col1 = col2 这样的语句,而 where 方法可以传递数组、子查询等等.

public function on($first, $operator = null, $second = null, $boolean = 'and')
{
    if ($first instanceof Closure) {
        return $this->whereNested($first, $boolean);
    }

    return $this->whereColumn($first, $operator, $second, $boolean);
}

public function orOn($first, $operator = null, $second = null)
{
    return $this->on($first, $operator, $second, 'or');
}

grammer——compileJoins
接下来我们来看看如何编译 join 语句:

protected function compileJoins(Builder $query, $joins)
{
    return collect($joins)->map(function ($join) use ($query) {
        $table = $this->wrapTable($join->table);

        return trim("{$join->type} join {$table} {$this->compileWheres($join)}");
    })->implode(' ');
}

可以看到,JoinClause 在编译中是作为 queryBuild 对象来看待的。

union 语句

union 用于合并两个或多个 SELECT 语句的结果集。Union 因为要进行重复值扫描,所以效率低。如果合并没有刻意要删除重复行,那么就使用 Union All。

我们在 laravel 中可以这样使用:

$query = DB::table('users')->select('*')->where('id', '=', 1);
$query->union(DB::table('users')->select('*')->where('id', '=', 2));
//(select * from `users` where `id` = 1) union (select * from `users` where `id` = 2)

还可以添加多个 union 语句:

$query = DB::table('users')->select('*')->where('id', '=', 1);
$query->union(DB::table('users')->select('*')->where('id', '=', 2));
$query->union(DB::table('users')->select('*')->where('id', '=', 3));      
//(select * from "users" where "id" = 1) union (select * from "users" where "id" = 2) union (select * from "users" where "id" = 3)

union 语句可以与 orderBy 相结合:

$query = DB::table('users')->select('*')->where('id', '=', 1);
$query->union(DB::table('users')->select('*')->where('id', '=', 2));
$query->orderBy('id', 'desc');
//(select * from `users` where `id` = ?) union (select * from `users` where `id` = ?) order by `id` desc

union 语句可以与 limit、offset 相结合:

$query = DB::table('users')->select('*');
$query->union(DB::table('users')->select('*'));
$builder->skip(5)->take(10);
//(select * from `users`) union (select * from `dogs`) limit 10 offset 5

union 函数
union 函数比较简单:

public function union($query, $all = false)
{
    if ($query instanceof Closure) {
        call_user_func($query, $query = $this->newQuery());
    }

    $this->unions[] = compact('query', 'all');

    $this->addBinding($query->getBindings(), 'union');

    return $this;
}

语法编译器对 union 的处理:

public function compileSelect(Builder $query)
{
    $sql = parent::compileSelect($query);

    if ($query->unions) {
        $sql = '('.$sql.') '.$this->compileUnions($query);
    }

    return $sql;
}

protected function compileUnions(Builder $query)
{
    $sql = '';

    foreach ($query->unions as $union) {
        $sql .= $this->compileUnion($union);
    }

    if (! empty($query->unionOrders)) {
        $sql .= ' '.$this->compileOrders($query, $query->unionOrders);
    }

    if (isset($query->unionLimit)) {
        $sql .= ' '.$this->compileLimit($query, $query->unionLimit);
    }

    if (isset($query->unionOffset)) {
        $sql .= ' '.$this->compileOffset($query, $query->unionOffset);
    }

    return ltrim($sql);
}

protected function compileUnion(array $union)
{
    $conjuction = $union['all'] ? ' union all ' : ' union ';

    return $conjuction.'('.$union['query']->toSql().')';
}

可以看出,union 的处理比较简单,都是调用 query->toSql 语句而已。值得注意的是,在处理 union 的时候,要特别处理 order、limit、offset。

orderBy 语句

orderBy 语句用法很简单,可以设置多个排序字段,也可以用原生排序语句:

DB::table('users')->select('*')->orderBy('email')->orderBy('age', 'desc');

DB::table('users')->select('*')->orderBy('email')->orderByRaw('age desc');

如果当前查询中有 union 的话,排序的变量会被放入 unionOrders 数组中,这个数组只有在 compileUnions 函数中才会被编译成 sql 语句。否则会被放入 orders 数组中,这时会被 compileOrders 处理:

public function orderBy($column, $direction = 'asc')
{
    $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
        'column' => $column,
        'direction' => strtolower($direction) == 'asc' ? 'asc' : 'desc',
    ];

    return $this;
}

grammar中orderBy 的编译也很简单:

protected function compileOrders(Builder $query, $orders)
{
    if (! empty($orders)) {
        return 'order by '.implode(', ', $this->compileOrdersToArray($query, $orders));
    }

    return '';
}

protected function compileOrdersToArray(Builder $query, $orders)
{
    return array_map(function ($order) {
        return ! isset($order['sql'])
                    ? $this->wrap($order['column']).' '.$order['direction']
                    : $order['sql'];
    }, $orders);
}

group 语句

groupBy 语句的参数形式有多种:

DB::select('*')->from('users')->groupBy('email');

DB::select('*')->from('users')->groupBy('id', 'email');

DB::select('*')->from('users')->groupBy(['id', 'email']);

DB::select('*')->from('users')->groupBy(new Raw('DATE(created_at)'));

groupBy 函数很简单,仅仅是为 $this->groups 成员变量合并数组:

public function groupBy(...$groups)
{
    foreach ($groups as $group) {
        $this->groups = array_merge(
            (array) $this->groups,
            Arr::wrap($group)
        );
    }

    return $this;
}

语法编译器的处理:

protected function compileGroups(Builder $query, $groups)
{
    return 'group by '.$this->columnize($groups);
}

insert 语句

insert 语句也是我们经常使用的数据库操作,它的源码如下:

public function insert(array $values)
{
    if (empty($values)) {
        return true;
    }

    if (! is_array(reset($values))) {
        $values = [$values];
    }
    else {
        foreach ($values as $key => $value) {
            ksort($value);

            $values[$key] = $value;
        }
    }

    return $this->connection->insert(
        $this->grammar->compileInsert($this, $values),
        $this->cleanBindings(Arr::flatten($values, 1))
    );
}

laravel 的 insert 是允许批量插入的,方法如下:

DB::table('users')->insert([['email' => 'foo', 'name' => 'taylor'], ['email' => 'bar', 'name' => 'dayle']]);

一个语句可以向数据库插入两条记录。sql 语句为:

insert into users (email,name) values ('foo', 'taylor'), ('bar', 'dayle');
因此,laravel 在处理 insert 的时候,首先会判断当前的参数是单条插入还是批量插入。

if (! is_array(reset($values))) {
    $values = [$values];
}

reset 会返回 values 的第一个元素。如果是批量插入的话,第一个元素必然也是数组。如果的单条插入的话,第一个元素是列名与列值。因此如果是单条插入的话,会在最外层再套一个数组,统一插入的格式。

如果是批量插入的话,首先需要把插入的各个字段进行排序,保证插入时各个记录的列顺序一致。

grammar中compileInsert对 insert 的编译也是按照批量插入的标准来进行的:

public function compileInsert(Builder $query, array $values)
{
    $table = $this->wrapTable($query->from);

    if (! is_array(reset($values))) {
        $values = [$values];
    }

    $columns = $this->columnize(array_keys(reset($values)));

    $parameters = collect($values)->map(function ($record) {
        return '('.$this->parameterize($record).')';
    })->implode(', ');

    return "insert into $table ($columns) values $parameters";
}

首先对插入的列名进行 columnze 函数处理,之后对每个记录的插入都调用 parameterize 函数来对列值进行处理,并用 () 包围起来。

update 语句

public function update(array $values)
{
    $sql = $this->grammar->compileUpdate($this, $values);

    return $this->connection->update($sql, $this->cleanBindings(
        $this->grammar->prepareBindingsForUpdate($this->bindings, $values)
    ));
}

与插入语句相比,更新语句更加复杂,因为更新语句必然带有 where 条件,有时还会有 join 条件:

public function compileUpdate(Builder $query, $values)
{
    $table = $this->wrapTable($query->from);

    $columns = collect($values)->map(function ($value, $key) {
        return $this->wrap($key).' = '.$this->parameter($value);
    })->implode(', ');

    $joins = '';

    if (isset($query->joins)) {
        $joins = ' '.$this->compileJoins($query, $query->joins);
    }

    $wheres = $this->compileWheres($query);

    return trim("update {$table}{$joins} set $columns $wheres");
}

其中有一个我们也比较常用的功能,updateOrInsert,其语句会先根据 attributes 条件查询,如果查询失败,就会合并 attributes 与 values 两个数组,并插入新的记录。如果查询成功,就会利用 values 更新数据。

public function updateOrInsert(array $attributes, array $values = [])
{
    if (! $this->where($attributes)->exists()) {
        return $this->insert(array_merge($attributes, $values));
    }

    return (bool) $this->take(1)->update($values);
}

delete 语句

删除语句比较简单,参数仅仅需要 id 即可,delete 语句会添加 id 的 where 条件:

public function delete($id = null)
{
    if (! is_null($id)) {
        $this->where($this->from.'.id', '=', $id);
    }

    return $this->connection->delete(
        $this->grammar->compileDelete($this), $this->getBindings()
    );
}

删除语句的编译需要先编译 where 条件:

public function compileDelete(Builder $query)
{
    $wheres = is_array($query->wheres) ? $this->compileWheres($query) : '';

    return trim("delete from {$this->wrapTable($query->from)} $wheres");
}

动态 where

laravel 有一个有趣的功能:动态 where。
DB::table('users')->whereFooBarAndBazOrQux('corge', 'waldo', 'fred')
这个语句会生成下面的 sql 语句:
select * from users where foo_bar = 'corge' and baz = 'waldo' or qux = 'fred';
也就是说,动态 where 将函数名解析为列名与连接条件,将参数作为搜索的值。

我们先看源码:

public function dynamicWhere($method, $parameters)
{

    $finder = substr($method, 5);

    $segments = preg_split(
        '/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE
    );

    $connector = 'and';

    $index = 0;

    foreach ($segments as $segment) {
        if ($segment !== 'And' && $segment !== 'Or') {
            $this->addDynamic($segment, $connector, $parameters, $index);

            $index++;
        }

        else {
            $connector = $segment;
        }
    }

    return $this;
}

protected function addDynamic($segment, $connector, $parameters, $index)
{
    $bool = strtolower($connector);

    $this->where(Str::snake($segment), '=', $parameters[$index], $bool);
}

总结

其实原文的查询构造器是分为三篇的,但是我这里只提出了常用的一些语句,然后汇总成了一篇,当然会丢失很多语句的处理,不过也能给出一个全貌,这也算是我个人的一个习惯吧。下一篇开始Eloquent Model 源码分析。

引用

Laravel 官方文档 -- 查询构造器
Laravel Database——查询构造器与语法编译器源码分析 (上)
Laravel Database——查询构造器与语法编译器源码分析 (中)
Laravel Database——查询构造器与语法编译器源码分析 (下)
Database 查询构建器
如何写一个属于自己的数据库封装(6) - 查询 - WHERE篇

以上

欢迎大家关注我的公众号


半亩房顶
上一篇下一篇

猜你喜欢

热点阅读