如何写一个属于自己的数据库封装(6) - 查询 - WHERE篇

上一期 如何写一个属于自己的数据库封装(5) - 查询 - JOIN篇
下一期 如何写一个属于自己的数据库封装(7) - UPDATE篇

开始之前

WHERE 篇中, where 的各种应用方式都来自 Laravel
本篇将在 Builder.php 的每个 where 函数最后边给出例子方便理解


Builder.php

  • where - 基本的 where 用法

where 函数用法多样, 请参考例子

public function where($column, $operator = null, $value = null, $boolean = 'and') {
        // 判定$column是否为数组
        if (is_array($column)) {
            // 如果是数组, 循环调用 where()
            foreach ($column as $condition) {
                // 一组只有两个参数意味着 $operator 被省略了, 自动加上默认的 '='
                if (sizeof($condition)===2)
                    list($condition[1], $condition[2]) = ['=', $condition[1]];

                // 不存在第四个参数意味着这是默认的 and 条件
                if(!isset($condition[3]))
                    $condition[3] = $boolean;

                // 这种调用方式必须提供最少2种参数, $columns, $operator, $value, $boolean可以选择默认, '='
                $this->where($condition[0], $condition[1], $condition[2], $condition[3]);
            }
        // 判定$column是否为闭包函数
        }elseif ($column instanceof Closure) {
            // 如果是, 调用 whereNested()
            // whereNested() 实现了高级 where 语句, 开发者可以实现的 SQL 语句 如下
            // select * from `actor` where `actor_id` = '5' or (`actor_id` = '6' and `first_name` != 'JACK')
            return $this->whereNested($column, $boolean);
        }else {
            // 反之, 判定参数数量和$value是否为空, 如果是,这意味着用户省略了'=',自动添加
            if(func_num_args() == 2 || is_null($value))
                list($operator, $value) = ['=', $operator];

            // 最简单原始的条件查询, 所以 $type 值为Basic
            $type = "Basic";
            // column 如果没有附上表名, 那就自动加上
            if (!preg_match('/\./', $column))
                $column = $this->model->getTable().".$column";
            // 将处理过的条件存入 $wheres
            $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
            // 将字段需要匹配的值存入 $bindings中的 where
            $this->addBinding($value, 'where');
        }
        // 返回 Builder 实例
        return $this;
    }


WHERE 的例子

1.以参数的方式 - 选择默认 '='

Actor::where('first_name', 'PENELOPE')->get();

2.以参数的方式 - 提供运算符

Actor::where('first_name', '!=', 'PENELOPE')->get();

3.以数组的方式 - 允许省略运算符和提供运算符两种方式混合使用

Actor::where([
            ['first_name', '!=', 'PENELOPE'],
            ['last_name', 'CHASE']
        ])->get();

4.允许多次调用

Actor::where('first_name', '!=', 'PENELOPE')
    ->where('last_name', 'CHASE')
    ->get();

5.以闭包函数来调用子句

select * from Actor where Actor.first_name = 'PENELOPE' and (Actor.last_name = 'PINKETT' or Actor.last_name = 'CRONYN')
Actor::where('first_name', 'PENELOPE')
    ->where(function ($builder) {
        $builder->where('last_name', 'PINKETT')
                ->orWhere('last_name', 'CRONYN'); // orWhere() 将在下方给出
    })
    ->get();

  • whereNested - 实现嵌套式的 where 语句
    whereNested() 仅供内部函数使用以实现闭包函数内的 where 子句, 所以没有例子
    public function whereNested(Closure $callback, $boolean = 'and') {
        // 创建一个新的 Builder 实例, 然后在闭包函数内应用它
        call_user_func($callback, $query = (new static)->from($this->from));

        // 这是一个嵌套
        $type = 'Nested';

        // 处理过的条件存入 $wheres
        $this->wheres[] = compact('type', 'query', 'boolean');

        // 将字段需要匹配的值存入 $bindings中的 where
        $this->addBinding($query->getBindings(), 'where');

        // 返回 Builder 实例
        return $this;
    }
  • orWhere - 所有逻辑同where(), 不过这是or where
    例子参照上面的 where 函数
    public function orWhere($column, $operator = null, $value = null) {
        return $this->where($column, $operator, $value, 'or');
    }
  • whereBetween - 字段匹配限定范围内的值
    public function whereBetween($column, array $values, $boolean = 'and', $not = false) {
        // 类型是 between
        $type = 'Between';

        // 处理过的条件存入 $wheres
        $this->wheres[] = compact('column', 'type', 'boolean', 'not');

        // 将字段需要匹配的值存入 $bindings中的 where
        $this->addBinding($values, 'where');

        // 返回 Builder 实例
        return $this;
    }

whereBetween 的例子

select * from Actor where Actor.actor_id between 5 and 8
Actor::whereBetween('actor_id', [5,8])->get();

  • whereBetween 的各种变体
    // 所有逻辑同 whereBetween(), 不过这是 or where between
    public function orWhereBetween($column, array $values) {
        return $this->whereBetween($column, $values, 'or');
    }

    // 所有逻辑同 whereBetween(), 不过这是 where not between
    public function whereNotBetween($column, array $values, $boolean = 'and')    {
        return $this->whereBetween($column, $values, $boolean, true);
    }

    // 所有逻辑同 whereNotBetween(), 不过这是 or where not between
    public function orWhereNotBetween($column, array $values)    {
        return $this->whereNotBetween($column, $values, 'or');
    }
  • whereIn - 字段匹配多个值
    public function whereIn($column, $values, $boolean = 'and', $not = false) {
        // 判定条件查询的类型, false = where in ($value),true = where not in ($value)
        $type = $not ? 'NotIn' : 'In';
        // 将条件存入$wheres
        $this->wheres[] = compact('type', 'column', 'values', 'boolean');
        // 循环将字段需要匹配的值存入$bindings中的where
        foreach ($values as $value)
            $this->addBinding($value, 'where');

        // 返回Builder实例
        return $this;
    }

whereIn 的例子

select * from Actor where Actor.actor_id in (5, 6, 8)
Actor::whereIn('actor_id', [5, 6, 8])->get();

  • whereIn 的各种变体
    // 所有逻辑同whereIn(), 不过这是or where in
    public function orWhereIn($column, $values) {
        return $this->whereIn($column, $values, 'or');
    }

    // 所有逻辑同whereIn(), 不过这是and where not in
    public function whereNotIn($column, $values, $boolean = 'and') {
        return $this->whereIn($column, $values, $boolean, true);
    }

    // 所有逻辑同whereNotIn(), 不过这是or where not in
    public function orWhereNotIn($column, $values) {
        return $this->whereNotIn($column, $values, 'or');
    }
  • whereNull - where $coulmn is null 语法, 搜索字段为空的数据
    public function whereNull($column, $boolean = 'and', $not = false) {
        // 判定条件查询的类型, false = where $column is null,true = where $column is not null
        $type = $not ? 'NotNull' : 'Null';
        // 将条件存入 $wheres
        $this->wheres[] = compact('type', 'column', 'boolean');
        // 返回 Builder 实例
        return $this;
    }

whereNull 的例子

select * from Actor where Actor.last_name is null
Actor::whereNull('last_name')->get();

  • whereNull 的各种变体
    // 所有逻辑同whereNull(), 不过这是or where $column is null
    public function orWhereNull($column) {
        return $this->whereNull($column, 'or');
    }

    // 所有逻辑同whereNull(), 不过这是and where $column is not null
    public function whereNotNull($column, $boolean = 'and') {
        return $this->whereNull($column, $boolean, true);
    }

    // 所有逻辑同whereNotNull(), 不过这是or where $column is not null
    public function orWhereNotNull($column) {
        return $this->whereNotNull($column, 'or');
    }
  • whereColumn - 用于比较字段, 比方说筛选出支出大于收入的记录
    public function whereColumn($first, $operator = null, $second = null, $boolean = 'and') {
        // 判定$column是否为数组
        if (is_array($first)) {
            // 如果是数组, 循环调用 whereColumn()
            foreach ($first as $condition) {
                // 一组只有两个参数意味着 $operator 被省略了, 自动加上默认的 '='
                if (sizeof($condition)===2)
                    list($condition[1], $condition[2]) = ['=', $condition[1]];

                // 不存在第四个参数意味着这是默认的 and 条件
                if(!isset($condition[3]))
                    $condition[3] = $boolean;

                // 这种调用方式必须提供最少2种参数, $columns, $operator, $value, $boolean可以选择默认, '='
                $this->whereColumn($condition[0], $condition[1], $condition[2], $condition[3]);
            }
        }else {
            // 反之, 判定参数数量和 $second 是否为空, 如果是,这意味着用户省略了'=',自动添加
            if(func_num_args() == 2 || is_null($second))
                list($operator, $second) = ['=', $operator];

            // 类型为 Column
            $type = 'Column';

            // 将条件存入 $wheres
            $this->wheres[] = compact(
                'type', 'first', 'operator', 'second', 'boolean'
            );
        }
        // 返回 Builder 实例
        return $this;
    }

whereColumn 的例子

select * from Actor where Actor.first_name = Actor.last_name
Actor::whereColumn('first_name', '=', 'last_name')->get()

或省略 '='

Actor::whereColumn('first_name', 'last_name')->get()

  • whereColumn 的变体
    // 所有逻辑同 whereColumn(), 不过这是 or where $column1 compare $column2
    public function orWhereColumn($first, $operator = null, $second = null) {
        return $this->whereColumn($first, $operator, $second, 'or');
    }

Grammar.php

配合注释味道更佳

  • compileWheres - 编译where语句
    protected function compileWheres(Builder $query) {
        $sql = [];
        // 类似与compileComponents(), 循环Builder实例中的$wheres
        foreach ($query->wheres as $where) {
            // 根据不同的$type来进行编译
            $method = "where{$where['type']}";
            // 返回的部分语句先收入数组
            $sql[] = $where['boolean'].' '.$this->$method($query, $where);
        }
        // 最后将$sql数组连接起来, 删掉最前面的and或or在返回
        return 'where '.preg_replace('/and |or /i', '', implode(" ", $sql), 1);
    }
  • whereBasic - 编译最基本的 where 用法
    protected function whereBasic(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点
        // 是, 就意味着前缀已经附带了表名
        // 否, 为之后的字段添上表名
        // 因为 join 存在副表, 所以部分 $where 可能有附带表名, 这时候就不用添加了
        $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
        // 返回添上表名的字段,和表达式, 再一个问号
        // 为何使用问号而不是:变量名? 因为:变量名存在太多局限性,不能标点符号,不能数字开头
        return $table.$where['column'].' '.$where['operator'].' ?';
    }
  • whereNested - 编译 where 嵌套分组用法
    protected function whereNested(Builder $query, $where) {
        return '('.str_replace('where ', '', $this->compileWheres($where['query'])).')';
    }
  • where between - 编译 where between 用法
    protected function whereBetween(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点, 同理 whereBasic()
        $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
        // 检测 $not, 如果是 true, 添上 'not'
        $between = $where['not'] ? 'not between' : 'between';

        return $table.$where['column'].' '.$between.' ? and ?';
    }
  • where in - 编译 where in 用法
    protected function whereIn(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点, 同理 whereBasic()
        $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
        // 有多少个匹配值那就连接多少个问号
        $values = implode(', ', array_fill(0, sizeof($where['values']), '?'));
        // 返回 where in 语句
        return $table.$where['column'].' in ('.$values.')';
    }
  • where not in - 编译 where not in 用法
    protected function whereNotIn(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点, 同理 whereBasic()
        $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
        // 有多少个匹配值那就连接多少个问号
        $values = implode(', ', array_fill(0, sizeof($where['values']), '?'));
        // 返回 where not in 语句
        return $table.$where['column'].' not in ('.$values.')';
    }
  • where null - 编译 where null 用法
    protected function whereNull(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点, 同理 whereBasic()
        $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
        // 返回 where is null 语句
        return $table.$where['column'].' is null';
    }
  • where not null - 编译 where not null 用法
    protected function whereNotNull(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点, 同理 whereBasic()
        $table = !preg_match('/\./', $where['column']) ? $query->from."." : '';
        // 返回where is not null 语句
        return $table.$where['column'].' is not null';
    }

  • where column - 编译 where column 用法
    protected function whereColumn(Builder $query, $where) {
        // 检测 $where[column] 是否存在小数点, 同理 whereBasic()
        $table = !preg_match('/\./', $where['first']) ? $query->from."." : '';

        // 为了避免开发者疏忽, $first 和 $second 其中一个没有加上前缀
        if($table !== '')
            $where['second'] = str_replace("$table.", '', $where['second']);

        return $table.$where['first'].' '.$where['operator'].' '.$table.$where['second'];
    }

完整代码

源代码放在coding.net里, 自己领

上一期 如何写一个属于自己的数据库封装(5) - 查询 - JOIN篇
下一期 如何写一个属于自己的数据库封装(7) - UPDATE篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容