Database 查询构建器
前面的文章我们介绍了Laravel Database的基础组件,这篇文章详细地介绍一下Database里非常重要的基础组件查询构建器
文章里我就直接用QueryBuilder来指代查询构建器
了。
上文我们说到执行DB::table('users')->get()
是由Connection对象执行table方法返回了一个QueryBuilder对象,QueryBuilder提供了一个方便的接口来创建及运行数据库查询语句,开发者在开发时使用QueryBuilder不需要写一行SQL语句就能操作数据库了,使得书写的代码更加的面向对象,更加的优雅。
1 | 复制代码class MySqlConnection extends Connection |
通过上面的代码段可以看到Connection类的构造方法里出了注入了Connector数据库连接器(就是参数里的$pdo
),还加载了两个重要的组件Illuminate\Database\Query\Grammars\Grammar
: SQL语法编译器 和 Illuminate\Database\Query\Processors\Processor
: SQL结果处理器。 我们看一下Connection的table方法,它返回了一个QueryBuilder实例, 其在实例化的时候Connection实例、Grammer实例和Processor实例会被作为参数传人QueryBuilder的构造方法中。
接下我们到QueryBuilder类文件\Illuminate\Database\Query\Builder.php
里看看它里面的源码
1 | 复制代码namespace Illuminate\Database\Query; |
QueryBuilder构建SQL参数
下面再来看看where方法里都执行里什么, 为了方便阅读我们假定执行条件where('name', '=', 'James')
1 | 复制代码//class \Illuminate\Database\Query\Builder |
所以上面DB::table(‘users’)->where(‘name’, ‘=’, ‘James’)执行后QueryBuilder对象里的几个属性分别有了一下变化:
1 | 复制代码public $from = 'users'; |
通过bindings属性里数组的key大家应该都能猜到如果执行select、orderBy等方法,那么这些方法就会把要绑定的值分别append到select和order这些数组里了,这些代码我就不贴在这里了,大家看源码的时候可以自己去看一下,下面我们主要来看一下get方法里都做了什么。
1 | 复制代码//class \Illuminate\Database\Query\Builder |
在执行get方法后,QueryBuilder首先会利用grammar实例编译SQL语句并执行,然后利用Processor实例处理结果集,最后返回经过处理后的结果集。 我们接下来看下这两个流程。
Grammar将构建的SQL参数编译成SQL语句
我们接着从toSql()
方法开始接着往下看Grammar类
1 | 复制代码public function toSql() |
在Grammar中,将SELECT语句分成来很多单独的部分放在了$selectComponents
属性里,执行compileSelect时程序会检查QueryBuilder设置了$selectComponents
里的哪些属性,然后执行已设置属性的编译器编译出每一部分的SQL来。
还是用我们之前的例子DB::table('users')->where('name', 'James')->get()
,在这个例子中QueryBuilder分别设置了cloums
(默认*)、from
、wheres
属性,那么我们见先来看看这三个属性的编译器:
1 | 复制代码/** |
compileColumns执行完后compileComponents里的变量$sql的值会变成['columns' => 'select * ']
接下来看看from
和wheres
部分
1 | 复制代码protected function compileFrom(Builder $query, $table) |
每一种where查询(orWhere, WhereIn……)都有它自己的编译器函数来创建SQL语句,这帮助保持里代码的整洁和可维护性. 上面我们说过在执行DB::table('users')->where('name', 'James')->get()
时$wheres属性里的值是:
1 | 复制代码public $wheres = [ |
在compileWheresToArray方法里会用$wheres中的每个数组元素去回调执行闭包,在闭包里:
1 | 复制代码$where = ['type' => 'basic', 'column' => 'name', 'operator' => '=', 'value' => 'James', 'boolean' => 'and'] |
然后根据type值把$where和QeueryBuilder作为参数去调用了Grammar的whereBasic方法:
1 | 复制代码protected function whereBasic(Builder $query, $where) |
whereBasic的返回为字符串'where name = ?'
, compileWheresToArray方法的返回值为:
1 | 复制代码['and where name = ?'] |
然后通过concatenateWhereClauses方法将compileWheresToArray返回的数组拼接成where语句'where name = ?'
1 | 复制代码protected function concatenateWhereClauses($query, $sql) |
所以编译完from
和wheres
部分后compileComponents方法里返回的$sql的值会变成
1 | 复制代码['columns' => 'select * ', 'from' => 'users', 'wheres' => 'where name = ?'] |
然后在compileSelect方法里将这个由查查询语句里每部份组成的数组转换成真正的SQL语句:
1 | 复制代码protected function concatenate($segments) |
得到'select * from uses where name = ?'
. toSql
执行完了流程再回到QueryBuilder的runSelect
里:
1 | 复制代码protected function runSelect() |
Connection执行SQL语句
$this->getBindings()
会获取要绑定到SQL语句里的值, 然后通过Connection实例的select方法去执行这条最终的SQL
1 | 复制代码public function select($query, $bindings = [], $useReadPdo = true) |
在Connection的select方法里会把sql语句和绑定值传入一个闭包并执行这个闭包:
1 | 复制代码function ($query, $bindings) use ($useReadPdo) { |
直到getPdoForSelect这个阶段Laravel才会连接上Mysql数据库:
1 | 复制代码protected function getPdoForSelect($useReadPdo = true) |
我们在上一篇文章里讲过构造方法里$this->pdo = $pdo;
这个$pdo参数是一个包装里Connector的闭包:
1 | 复制代码function () use ($config) { |
所以在getPdo阶段才会执行这个闭包根据数据库配置创建连接器来连接上数据库并返回PDO实例。接下来的prepare、bindValues以及最后的execute和fetchAll返回结果集实际上都是通过PHP原生的PDO和PDOStatement实例来完成的。
通过梳理流程我们知道:
- Laravel是在第一次执行SQL前去连接数据库的,之所以$pdo一开始是一个闭包因为闭包会保存创建闭包时的上下文里传递给闭包的变量,这样就能延迟加载,在用到连接数据库的时候再去执行这个闭包连上数据库。
- 在程序中判断SQL是否执行成功最准确的方法是通过捕获
QueryException
异常
Processor后置处理结果集
processor是用来对SQL执行结果进行后置处理的,默认的processor的processSelect方法只是简单的返回了结果集:
1 | 复制代码public function processSelect(Builder $query, $results) |
之后在QueryBuilder的get方法里将结果集转换成了Collection对象返回给了调用者.
到这里QueryBuilder大体的流程就梳理完了,虽然我们只看了select一种操作但其实其他的update、insert、delete也是一样先由QueryBuilder编译完成SQL最后由Connection实例去执行然后返回结果,在编译的过程中QueryBuilder也会帮助我们进行防SQL注入。
本文已经收录在系列文章Laravel核心代码学习里,欢迎访问阅读。
本文转载自: 掘金