laravel 学习笔记 —— 查询构造器(下)

   2016-11-16 0
核心提示:来继续填坑(上个月没发,因此这个月至少两篇)。上一篇laravel 学习笔记 —— 查询构造器(下)中我们正在开始分析查询构造器的where方法,作为出场率最高的方法,其中相关的玩意儿足以帮助我们去理解整个组件大致的设计思路和实现细节。由于整体分析实在是

来继续填坑(上个月没发,因此这个月至少两篇)。

上一篇 laravel 学习笔记 —— 查询构造器(下) 中我们正在开始分析查询构造器的 where 方法,作为出场率最高的方法,其中相关的玩意儿足以帮助我们去理解整个组件大致的设计思路和实现细节。由于整体分析实在是太过麻烦(我其实就是懒),因此我们在上一篇的末尾提了三个问题,本文也将顺着这三个问题来进行解析,当然其余的思路类似的,就不在赘述,毕竟精力有限。

上一篇提到的问题是:

  • where 方法中多余的参数是干什么的
  • 方法中的代码为什么要拆分(或者封装)的那么细 ?
  • Expression 这个类是干嘛的 ?

我们就从多余的参数是做什么的开始吧。

开始

where 方法所包含的参数有 4 个(Laravel 5.1,这几篇文章都是基于这个和 5.2 两个版本讲述),分别为: $column , $operator , $value , $boolean

顾名思义,第一个参数必然是指的字段名称,第二个参数则是操作符,第三个是值,第四个指的是布尔逻辑。除了第一个参数,其余皆包含默认值意味着可选填。从参数名和你们所阅读 Laravel 文档中所知晓的,where 的用法基本已经了解的差不多了,但是第四个参数对于各位应该没在实际项目中使用过,实际上如果你们愿意去翻一翻源码,比如 orWhere ,你就会发现这些方法本质还是调用的 where ,只是这参数 4 变了,至于为什么封装那么细,这就是第二个问题,我们后文会一并讲。

为了方便分析,我还是把 where 的那段代码放出来:

public function where($column, $operator = null, $value = null, $boolean = 'and')
{
    if (is_array($column)) {
        return $this->addArrayOfWheres($column, $boolean);
    }

    if (func_num_args() == 2) {
        list($value, $operator) = [$operator, '='];
    } elseif ($this->invalidOperatorAndValue($operator, $value)) {
        throw new InvalidArgumentException('Illegal operator and value combination.');
    }

    if ($column instanceof Closure) {
        return $this->whereNested($column, $boolean);
    }

    if (! in_array(strtolower($operator), $this->operators, true) &&
        ! in_array(strtolower($operator), $this->grammar->getOperators(), true)) {
        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 != '=');
    }

    $type = 'Basic';
    if (Str::contains($column, '->') && is_bool($value)) {
        $value = new Expression($value ? 'true' : 'false');
    }
    $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
    if (! $value instanceof Expression) {
        $this->addBinding($value, 'where');
    }
    return $this;
}

源码简析

执行过程的开始

代码中,首先开始就是判断参数 1 是否为数组(有的人可能以其他版本作参考,有差异很正常,但原理类似,这里是 5.2 的写法),若为数组则类似于 TP 框架的处理方案,将数组的 key 作为字段, value 作为值,操作符为 = 的 WHERE 语句,布尔逻辑为 AND,例如 $query->where(['a' => 100, 'b' => 200]); 转换的结果即为 WHERE a = 100 AND b = 200

若参数 1 不是数组,且参数只填写了两个,那么操作符默认为 = ,并将参数 2 的值用于实际的参数 3。这种使用形式主要用于快速建立 WHERE 中的等值判断。

我们继续阅读代码,可以看到判断参数 1 是否为 Closure 的代码,若满足条件则会调用一个叫做 whereNested 的方法,这个方法的作用十分重要,实现了在上一篇中我们提到的 Laravel 查询构造器优雅特性中的关键部分,即可以在保证生成复杂层级关系的 SQL 语句的前提下,PHP 部分的代码调用非常直观,通俗点说就是能让语句中 带括号 ,虽然不是什么不得了的功能,但是其他框架或类似模块或多或少调用不直观,甚至对于这种功能还是逃不了原生写法,在这样的对比下,就显得难能可贵。

在这里需要强调一点:并不是所有情况都建议和鼓励使用 PHP 的方式和写法去替代 SQL 原生语句,考虑到性能以及其他业务情形,在没有框架或框架性能较低的情况,完全没必要用复杂的东西去做,怎么快怎么好。至于在本文中对这些特性的讲述,更多是帮助 学习 PHP 的朋友,了解一些思想,包括很多 PHP 的新特性和功能函数是如何被巧妙运用并解决问题的,这才是这一系列文章的作用。

关于 whereNested 的实现原理,实际和 where 方法的实现原理类似,只是有逻辑变动,设计思想是一样的,因此继续分析 where 的实现。

为什么拆分那么细

随着代码往后阅读,我们可以看到更多的对于值类型的判断以及在种种条件下,将处理的东西交由另一个对应的方法处理。在这里就讲讲之前的问题,拆分这么细的意义。

我们封装以及再封装(通俗所说的拆分)代码的目的有很多,每个人的出发点或多或少都有着区别,但目的不是便于维护(或复用)就是转换调用。转换调用的典型就是我们在 where 方法的参数 4,一般是不会用的,我们常常在 where 或 orWhere 中切换,而 orWhere 就是 where 的参数 4 的值为 or 而已,但是很显然的 where(x, x, x, 'or') 和 orWhere(x, x, x) 哪个更直观一眼便知,转换调用还往往用于创建方法以及函数的别名,对于一些项目重构和调整 API 规则时常常用到。

更多的情况,封装是为了便于维护,毕竟重复代码不会总是去每个地方都去改。这时候就存在一个问题,由于这种目的本身就是一个没有标准的,因此封装质量参差不齐,出现注入所谓的一切都要封装的过度现象(最终导致和没有封装毫无差别,甚至更糟)。

任何程序都是实现一定功能的。那么我们看到的,Laravel 在做封装的时候,往往会将一类特定功能的局部代码进行封装,而且这些封装出来的方法和函数, 往往只会做一件事 !这样的好处就使得在思路上不存在混乱的可能,而且由于只做一件事,给方法命名就按其所做工作命名即可,一眼就知道这行代码是做什么的,那么便于维护的目的才真的达到了。

调用转换为 SQL 的开始

我们在阅读到代码最后,看到了这样一行 $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');

如果你读的代码足够多,你会发现所有框架或类似模块中实现查询构造器时,都会有类似的部分,很显然,我们目的是使用构造器生成 SQL 语句,那么必然要在某个地方记录我们在程序中的参数作为生成 SQL 语句的依据。

这没什么神秘的。

Laravel 实现的方式需要保证功能够强,够直观,因此在这里记录的东西也显得非常丰富。注意到一个函数没有,就是 compact ,估计读本文的大多数读者一般都未曾接触过,建议各位去文档上查一下,在这里就不多讲。我们来看看在此处记录的东西分别是什么。

首先是 type,type 记录的是该条 where 语句的类型,如果我们调用的是 where 方法,则该值为 "Basic",当然,我们前面还提到了 whereNested ,如果调用了这个,则 type 为 "Netsed"。

不同的 type 则记录的东西也有很大不同,对于 "Basic",则还需要记录 column (字段)、operator(操作符)、value(值)、boolean(布尔逻辑)。而另外的比如 Nested 则会记录 query(子查询,又是一个 Builder 对象哦)、boolean。

当记录完这一切,我们看到了另一个方法 addBinding ,向这个方法传递了 where 语句中的 value 值,这一步是做什么的呢?我们知道,对于 PDO,SQL 语句是可以预编译的,这样也很容易防御来自 SQL 语句注入的攻击,毕竟预编译将语句和语句中的变量彻底的分离,变量就是变量,不参与语句执行,这样安全系数大大提高。以上,我们可见到的再使用 PDO 时,我们会先写类似这样的 SQL 语句: SELECT * FROM table WHERE a = ? AND b = ? ,问号是一个表示该处为变量的位置,在 SQL 执行时传入,即所谓的参数绑定。

回到先前的问题,我们已经可以回答, addBinding 顾名思义就是添加一个参数的绑定,因为最终构造器以及我们后一篇文章中会讲述的(语法)生成器所产生的目标 SQL 语句,就是一个这样的 SQL 语句,在执行时才会传入参数。而 问号 的位置必然需要和所包含的值顺序对应,因此在添加 where 语句参数至 $query->wheres 数组时,同时也需要对应一个绑定参数以此对应。

额外细节

我们分析了 where 这个比较具有代表性的查询构造器的方法后,其实可以看出查询构造器的目的就是提供一系列直观的方法调用,这些方法的参数就是我们用于查询语句的参数,但是却不需要我们考虑诸如字段名加 ` 、表名称的前缀以及子查询、JOIN 语句的别名或者一些杂七杂八足以使你无限纠结的 SQL 语句细节。查询构造器在提供了这些方法的同时,还要保证调用足够直观和优雅。但最终在实际逻辑中,就是将参数收集并按需要排列组合成一定形式,最后交由(语法)生成器生成目标 SQL 语句。

不过这里有个细节,就是如何在这种形式下,依旧能够使用原生语句?开头的第三个问题, Expression 类是干什么的?

上面那一大段,如果各位仔细读,就会发现我提到了字段和表名称。在查询构造器中,有个方法:select 和 table。对于 select 我们常规就传递字段名就好,但对于特别的情形下,比如我们需要这样 SELECT count(x) as c FROM table ,我们很显然不可以 $query->select('count(x) as c') ,因为在(语法)生成器阶段,会将参数中属于字段、表名称做一些处理,例如在查询语句中存在调用了多个表的情形下,字段 x 可能会变成 table.x ,意味着 count(x) as c 会变成 table.count(x) as c ,当然实际情况不一定如此,但必定会发生类似情况,因为框架不可能将判断细化到如此到可以区分格式合不合理,那样性能开销会更加大。那么如何避免大面积原生语句的情况下在局部使用原生呢?

Expression 类就是这个用处。在构造这个类时,我们可以将局部的原生语句作为参数传入,并将其作为如 select、table、where 等一系列查询构造器方法的值,在最终由(语法)生成器生成目标 SQL 语句时,会判断是一个普通值还是一个 Expression 类,若为后者,则不再做转换处理。

instance of 这个语句用处十分广泛。

后面的内容

查询构造器不在多讲,更多内容可举一反三,因为落到实际原理,真的不复杂,未讲到的部分不代表不重要,因为查询构造器的细节十分多,但是只要愿意去看源码,很多东西很容易理解,这得益于 Laravel 的代码无论从命名还是逻辑结构,都看起来赏心悦目,所以,不要看到此就结束探索,继续看代码吧。

下一篇预告:查询构造器是生成语句的开始,真正生成语句是(语法)生成器,生成器和构造器都是一个在简单原理上,运用巧妙地办法实现复杂功能的,(语法)生成器最大的特点就是如何适应各种数据库驱动下的目标语句生成,这关系到一些架构设计的思路,我们下篇慢慢讲。

 
标签: Laravel SQL
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • nginx 各类网站设置 (laravel , thinkphp , nod
    基础部分设置[root@centos ~]# vim /opt/nginx/conf/nginx.confuser www www;worker_processes auto;pid logs/nginx.pid;worker_rlimit_nofile 100000;events {use epoll;multi_accept on;worker_connections 65535 ;}http {include mime.types;default_type
    02-09
  • PHP trait 特性在 Laravel 中的使用个人心得
    trait 是在PHP5.4中为了方便代码复用的一种实现方式,但目前我在看的的PHP项目中较少看的有程序员去主动使用这个实现方式,在laravel中有很多 trait 的使用,关于trait 在 laravel 的使用请参看 Laravel 在哪些地方用了 trait?我曾在 Laravel 中大型项目面向
    02-09
  • 让我们用 laravel-mix 为 TypeScript 和 Sass
    介绍前端编译TypeScript、Sass、模板引擎等时经常用到Gulp和webpack。这是我个人的印象,但它们似乎都难以管理,因为它们的描述往往复杂而冗长。我不想积极进行,因为我要担心加载器的顺序并且有很多配置选项,我必须花时间去了解它们。我想推荐那里laravel
  • PHP Laravel软删除的实现方法介绍
    用Laravel 自带的 Eloquent ORM 来实现软删除。首先在数据迁移文件中添加删除时间字段./database/migrations/2014_10_12_000000_create_users_table.php?phpuse Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illu
  • Laravel中如何使用PHP的装饰器模式 php laravel
    本文小编为大家详细介绍“Laravel中如何使用PHP的装饰器模式”,内容详细,步骤清晰,细节处理妥当,希望这篇“Laravel中如何使用PHP的装饰器模式”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。装饰器模式定义:它可以帮助您在
    02-08 laravelphp
  • PHP laravel使用自定义邮件类实现发送邮件
    PHP laravel使用自定义邮件类实现发送邮件
    当登录邮箱为腾讯企业邮箱的时候。Phpmailer发送邮件就不好用了,具体哪里不好用,我没真没找到。但是,邮件得发啊,怎么办呢?我这里搞了一个自定义的发送邮件类,腾讯企业邮箱也可用。但是,邮件发送失败,不会返回报错信息,这个可能是有点坑。源码如下:?
  • 详解PHP laravel中的加密与解密函数
    目录一:简介二:配置三:使用加密/解密1:加密2:不使用序列化进行加密3:解密Laravel为我们提供了完整的加密方法及加密模式。我之前一般在加密的时候使用的是我自己写的加密函数,但是这个玩意,有的位置还是不太使用,当然,破解的话,基本上也是不可能的
  • PHP laravel缓存cache机制详解
    目录一、访问多个缓存存储二、从缓存中获取数据1.获取数据并设置默认值2.检查缓存项是否存在3.数值增加/减少4.获取存储5.获取删除三、缓存中存储数据1.获取存储数据2.缓存不存在时存储数据3.永久存储数据四、从缓存中移除数据Laravel中的cache为我们提供了三
  • PHP laravel实现导出PDF功能
    PHP laravel实现导出PDF功能
    目录一、laravel-tcpdf二、tcpdf三、TCPDF解决保存中文文件名的方法补充一、laravel-tcpdf导出PDF文件Laravel框架为我们集成了一个插件tcpdf。下载地址:https://github.com/elibyy/tcpdf-laravel然后使用composer进行安装就可以了。具体安装过程,请查看文末
  • PHP laravel缓存cache机制怎么实现
    今天小编给大家分享一下PHP laravel缓存cache机制怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Laravel中的cache为我们
点击排行