Laravel 学习笔记之 Query Builder 源码解析(下)

说明:本文主要学习下Query Builder编译Fluent ApiSQL的细节和执行SQL的过程。实际上,上一篇聊到了\Illuminate\Database\Query\Builder这个非常重要的类,这个类含有三个主要的武器:MySqlConnection, MySqlGrammar, MySqlProcessorMySqlConnection主要就是在执行SQL时做连接MySql数据库操作,MySqlProcessor主要就是用来对执行SQL后的数据集做后置处理操作,这两点已经在之前上篇聊过,那MySqlGrammar就是SQL语法编译器,用来编译Fluent ApiSQL。最后使用MySqlConnection::select($sql, $bindings)执行SQL。

开发环境:Laravel5.3 + PHP7

Builder::toSql()

看下toSql()的源码:

    public function toSql(){// $this->grammar = new MySqlGrammarreturn $this->grammar->compileSelect($this);}public function compileSelect(Builder $query){$sql = parent::compileSelect($query);// 从上一篇文章知道,$unions属性没有存储值,$wheres属性是有值的if ($query->unions) {$sql = '('.$sql.') '.$this->compileUnions($query);}return $sql;}

这里首先会调用Illuminate\Database\Query\GrammarsGrammar::compileSelect(Builder $query),看下compileSelect(Builder $query)的源码:

    public function compileSelect(Builder $query){// $original = ['*']$original = $query->columns;if (is_null($query->columns)) {$query->columns = ['*'];}$sql = trim($this->concatenate($this->compileComponents($query)));$query->columns = $original;// $sql = 'select * from users where id = ?'return $sql;}protected $selectComponents = ['aggregate','columns','from','joins','wheres','groups','havings','orders','limit','offset','lock',];protected function compileComponents(Builder $query){$sql = [];foreach ($this->selectComponents as $component) {// if (! is_null($query->$component)) {$method = 'compile'.ucfirst($component);// 1. compileColumns($builder, ['*']) -> 'select ' . $this->columnize(['*'])// 2. compileFrom($builder, 'users'); -> 'from '.$this->wrapTable('users')// 3. compileWheres($builder, [ 0 => ['type' => 'basic', 'column' => 'id', 'operator' => '=', 'value' => 1, 'boolean' => 'and'], ])// $sql = ['columns' => 'select *', 'from' => 'from users', 'wheres' => 'where id = ?']$sql[$component] = $this->$method($query, $query->$component);}}return $sql;}

从上文源码中可知道,首先依次遍历片段集合:aggregate,columns,from,joins,wheres,groups,havings,orders,limit,offset,lock,查看属性有无存储值。在上文中知道,在片段$columns,from,wheres存有值为['*'], 'users', [['type' => 'basic', 'column' => 'id', 'operator' => '=', 'value' => 1, 'boolean' => 'and']],然后通过拼接字符串调用方法compileColumns($builder, ['*']), compileFrom($builder, 'users'), compileWheres($builder, array),依次看下这些方法的源码:

    protected function compileColumns(Builder $query, $columns){   if (! is_null($query->aggregate)) {return;}// $select = 'select '$select = $query->distinct ? 'select distinct ' : 'select ';return $select.$this->columnize($columns);}// Illuminate/Database/Grammarpublic function columnize(array $columns){// 依次经过wrap()函数封装下return implode(', ', array_map([$this, 'wrap'], $columns));}public function wrap($value, $prefixAlias = false){if ($this->isExpression($value)) {return $this->getValue($value);}if (strpos(strtolower($value), ' as ') !== false) {$segments = explode(' ', $value);if ($prefixAlias) {$segments[2] = $this->tablePrefix.$segments[2];}return $this->wrap($segments[0]).' as '.$this->wrapValue($segments[2]);}$wrapped = [];$segments = explode('.', $value);// $segments = ['*']foreach ($segments as $key => $segment) {if ($key == 0 && count($segments) > 1) {$wrapped[] = $this->wrapTable($segment);} else {// $wrapped = ['*']$wrapped[] = $this->wrapValue($segment);}}return implode('.', $wrapped);}protected function wrapValue($value){if ($value === '*') {return $value;}return '"'.str_replace('"', '""', $value).'"';}

通过源码很容易知道compileColumns($builder, ['*'])返回值select "*",然后将该值以key-value形式存储在$sql变量中,这时$sql = ['columns' => 'select "*"']
OK,看下compileFrom($builder,'users')源码:

    protected function compileFrom(Builder $query, $table){return 'from '.$this->wrapTable($table);}// Illuminate/Database/Grammarpublic function wrapTable($table){if ($this->isExpression($table)) {return $this->getValue($table);}// 返回"users"return $this->wrap($this->tablePrefix.$table, true);}

很容易知道返回值是from "users",然后将该值存储在$sql变量中,这时$sql = ['columns' => 'select "*"', 'from' => 'from "users"']。OK,看下compileWheres($builder, array)的源码:

    protected function compileWheres(Builder $query){$sql = [];if (is_null($query->wheres)) {return '';}foreach ($query->wheres as $where) {$method = "where{$where['type']}"; // 'whereBasic'// 'and ' . $this->whereBasic($builder, ['type' => 'basic', 'column' => 'id', 'operator' => '=', 'value' => 1, 'boolean' => 'and']// -> $sql = ['and id = ?', ];$sql[] = $where['boolean'].' '.$this->$method($query, $where);}if (count($sql) > 0) {$sql = implode(' ', $sql);// $conjunction = 'where'$conjunction = $query instanceof JoinClause ? 'on' : 'where';// 去除掉'and'字符后为'where id = ?'return $conjunction.' '.$this->removeLeadingBoolean($sql);}return '';}protected function whereBasic(Builder $query, $where){// $value = '?'$value = $this->parameter($where['value']);// 返回'id = ?'return $this->wrap($where['column']).' '.$where['operator'].' '.$value;}

从源码中可知道返回值为where id = ?,这时$sql = ['columns' => 'select "*"', 'from' => 'from "users"', 'wheres' => 'where id = ?']

OK, 最后通过concatenate()函数把$sql值拼接成字符串select "*" from "users" where id = ?

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

也就是说,通过SQL语法编译器MySqlGrammartable('users')->where('id', '=', 1)编译成了SQL语句select * from users where id = ?

MySqlConnection::select()

上文聊到Builder::runSelect()调用了三个方法:MySqlConnection::select(), Builder::toSql(), Builder::getBindings(),其中Builder::toSql()通过SQL语法编译器已经编译得到了SQL语句,Builder::getBindings()获取存储在$bindings[ ]的值。最后看下MySqlConnection::select()是如何执行SQL语句的:

    public function select($query, $bindings = [], $useReadPdo = true){// Closure就是用来执行SQL,并把$query = 'select * from users where id =?', $bindings = 1作为参数传递进去return $this->run($query, $bindings, function (Connection $me, $query, $bindings) use ($useReadPdo) {if ($me->pretending()) {return [];}// $statement = PDO::prepare('select * from users where id =?')/** @var \PDOStatement $statement */$statement = $this->getPdoForSelect($useReadPdo)->prepare($query);$me->bindValues($statement, $me->prepareBindings($bindings));//PDO三步走: SQL编译prepare() => 值绑定bindValue() => SQL执行execute()// PDO通过这种方式防止SQL注入$statement->execute();$fetchMode = $me->getFetchMode();$fetchArgument = $me->getFetchArgument();$fetchConstructorArgument = $me->getFetchConstructorArgument();if ($fetchMode === PDO::FETCH_CLASS && ! isset($fetchArgument)) {$fetchArgument = 'StdClass';$fetchConstructorArgument = null;}// PDOStatement::fetchAll(PDO::FETCH_OBJ);return isset($fetchArgument)? $statement->fetchAll($fetchMode, $fetchArgument, $fetchConstructorArgument): $statement->fetchAll($fetchMode);});}protected function run($query, $bindings, Closure $callback){$this->reconnectIfMissingConnection();$start = microtime(true);try {// 执行闭包函数$result = $this->runQueryCallback($query, $bindings, $callback);} catch (QueryException $e) {if ($this->transactions >= 1) {throw $e;}$result = $this->tryAgainIfCausedByLostConnection($e, $query, $bindings, $callback);}$time = $this->getElapsedTime($start);$this->logQuery($query, $bindings, $time);return $result;}protected function runQueryCallback($query, $bindings, Closure $callback){try {// 执行闭包函数$result = $callback($this, $query, $bindings);}catch (Exception $e) {throw new QueryException($query, $this->prepareBindings($bindings), $e);}return $result;}

通过源码知道主要是执行闭包来实现连接数据库和执行SQL操作,其中$statement = $this->getPdoForSelect($useReadPdo)->prepare($query)这句代码实现了数据库的连接操作SQL语句送入MySQL服务器进行语句编译。上文中提前聊了通过数据库连接器MySqlConnector::connect()连接数据库,这里知道实际上连接数据库是在这个时刻才触发的,Laravel5.0版本好像还没有这么写:

    protected function getPdoForSelect($useReadPdo = true){return $useReadPdo ? $this->getReadPdo() : $this->getPdo();}public function getPdo(){if ($this->pdo instanceof Closure) {// 连接数据库,获得PDO实例return $this->pdo = call_user_func($this->pdo);}return $this->pdo;}

通过源码知道执行SQL操作很简单,就是常见的PDO操作:PDO三步走: SQL编译PDO::prepare() => 值绑定PDOStatement::bindValue() => SQL执行PDOStatement::execute()。所以这里可看出Query Builder是在PHP PDO的基础上实现的一层封装,使得用更加面向对象的Fluent API来操作数据库,而不需要写一行SQL语句。

OK, 总的来说,通过了解Query Builder的实现原理后,知道其并不复杂或神秘,只是一个对PDO更友好封装的包裹,Query Builder有几个重要的类或概念:连接类MySqlConnection及其为其服务的连接器MySqlConnector;Builder 类;SQL语法解析器MySqlGrammar;后置处理器MySqlProcessor

OK, illuminate/database package不仅提供了Query Builder,还提供了Eloquent ORM。那Eloquent ORM又是什么,与Query Builder是什么关系呢?既然有了Query Builder,为何还提供了Eloquent ORM呢?
实际上,Eloquent ORM又是对Query Builder的封装,这样可以实现更多好用且Query Builder所没有的功能,如Model Relationships;Accessor/Mutator;Scopes等等。以后再聊Eloquent ORM的实现原理吧。

总结:本文主要学习了Query Builder编译SQL细节和执行SQL逻辑。后续在分享下Eloquent ORM的实现原理,到时见。

RightCapital招聘Laravel DevOps

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

更方便地模拟 Http 响应

更方便地 Mock Http ResponseIntro在我们的业务代码中往往会有很多调用内部其他 team 或者是第三方的一些服务,在编写单元测试代码时,往往需要 Mock Http Response 来模拟更好可能的返回结果,我封装了一个简单的 Http Handler 来简化 Mock 过…

男人的快乐可以多简单?

1 说不出哪里像但是非常像(素材来源网络,侵删)▼2 质量不错,就是风大不建议穿(素材来源网络,侵删)▼3 谈恋爱的要求(素材来源网络,侵删)▼4 当给狗狗看了捕…

Android插件化开发之用DexClassLoader加载未安装的APK资源文件来实现app切换背景皮肤

第一步、先制做一个有我们需要的图片资源的APK 如下图,这里有个about_log.png,我们需要生成apk文件。生成的apk文件如果你不到项目的文件夹里面去取apk,想通过命令放到手机里面去可以快速用下面命令 1)、在手机里面通过包名找到apk路径&…

ManualResetEvent实现线程的暂停与恢复

背景前些天遇到一个需求,在没有第三方源码的情况下,刷新一个第三方UI,并且拦截到其ajax请求的返回结果。当结果为AVALIABLE的时候,停止刷新并语音提示,否则继续刷新。分析这个需求,发现需要控制一个刷新循环…

浙大哈佛剑桥学者联手破解数学界几十年的谜题,成果登上数学顶刊

全世界只有3.14 % 的人关注了爆炸吧知识转自:量子位作者:边策 萧箫当两个看似“无关”的数学领域发生碰撞,会发生什么?浙江大学研究员、中科大数学系2003级校友叶和溪,与来自剑桥大学、哈佛大学的两位学者一起&#xf…

Hibernate 参数设置一览表

Hibernate 参数设置一览表属性名用途hibernate.dialect一个Hibernate Dialect类名允许Hibernate针对特定的关系数据库生成优化的SQL. 取值 full.classname.of.Dialect hibernate.show_sql输出所有SQL语句到控制台. 有一个另外的选择是把org.hibernate.SQL这个log category设为d…

jsp将鼠标放到那显示信息

将下面的代码&#xff0c;粘贴复制到jsp<body></body>中<A href"#" οnmοuseοver"document.getElementById(aaa).style.display" οnmοuseοut"document.getElementById(aaa).style.displaynone">鼠标移动到这我就出来了&l…

Monkey测试实例

Monkey是Android中的一个命令行工具&#xff0c;可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等)&#xff0c;实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。1…

.NET Core Runtime vs .NET Framework Runtime

在我从 .NET Framework 到 .NET Core 的过渡期间&#xff0c;有一件事情让我很长时间都感到困惑&#xff0c;那就是 Runtime 运行时&#xff0c; 实际上 Runtime 在 .NET Framework 和 .NET Core 中具有不同的含义。.NET Framework 运行时的历史 当有人问我们&#xff0c;什么是…

“玻璃大王”曹德旺捐资100亿办大学!幼年失学的他要打造理工科研究型大学...

全世界只有3.14 % 的人关注了爆炸吧知识本文转自募格学术整理自&#xff1a;量子位&#xff08;作者贾浩楠、鱼羊&#xff09;、青塔等100亿投资&#xff0c;福建省迎来第一个“新型大学”。办学资金&#xff0c;来自生长于福建&#xff0c;在福建发家致富的“玻璃大王”曹德旺…

JAVA配置Tomcat

1.下载tomcat&#xff0c;我jdk是1.8的&#xff0c;网上查了一下&#xff0c;说要安装tomcat8及以上的tomcat 尝试点击&#xff0c;弹出&#xff0c; 2.配置环境 3.安装通过cmd安装 4.点击开启服务 5.输入localhost:8080查看 6.还可以通过tomcat8w.exe来启动窗口&#xff0c;开…

女孩看男孩VS男孩看女孩

1 这波猫粮我吃了&#xff01;2 论一只狗子的自娱自乐精神&#xff01;3 分享一个 “想死你了”的拥抱&#xff01;4 老师&#xff1a;下课铃不代表这节课上完了5 谁怕谁啊&#xff01;6 好像很有道理的样子7 将它们展开会是什么&#xff1f;你点的每个赞&#xff0c;我都认真当…

使用建造者模式创建模拟数据

前言在写测试用例时&#xff0c;我们经常需要创建模拟数据&#xff0c;在C#中常用的方式是使用nuget包Bogus。Bogus可以按照一定规则生成随机数据&#xff0c;示例代码如下&#xff1a;public class User {public string Name { get; set; }public int Age { get; set; }public…

JMeter学习(六)集合点

JMeter也有像LR中的集合点&#xff0c;本篇就来介绍下JMeter的集合点如何去实现。 JMeter里面的集合点通过添加定时器来完成。 注意&#xff1a;集合点的位置一定要在Sample之前. 集合点&#xff1a;简单来理解一下&#xff0c;虽然我们的“性能测试”理解为“多用户并发测试”…

无意间看到的浏览器记录......

1 喵&#xff1a;我是风一样的喵主子...啊错了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 老了谁照顾你&#xff1f;▼3 帮妈妈清理手机垃圾▼4 你现在做什么工作&#xff1f;&#xff08;via.欢声笑语bot&#xff0c;侵删&#xff09;▼5 社会社会&#xf…

linux C/C++开发环境搭建指南

2019独角兽企业重金招聘Python工程师标准>>> Eclipse IDE for C/C developers 就是安装了CDT的Eclipse IDE了。 一、安装基本开发环境 本人想学习一下Linux下的C&#xff0c;C&#xff0b;&#xff0b;程序开发&#xff0c;这几天一直在研究Linux下的C语言编译环境…

网站能拿到其他网站的cookie_如何设计网站能让网站建设的更有吸引力

现在做互联网营销的企业基本都是有自己的企业网站&#xff0c;不过要做好企业网站不能简简单单的套个模板&#xff0c;这样做出来的网站毫无吸引力&#xff0c;做好的网站那么网站的设计的很重要的&#xff0c;换个思维大家想一下&#xff0c;网站的设计就和我们线下开门店装修…

.NET6之MiniAPI(九):基于角色的身份验证和授权

身份验证是这样一个过程&#xff1a;由用户提供凭据&#xff0c;然后将其与存储在操作系统、数据库、应用或资源中的凭据进行比较。 在授权过程中&#xff0c;如果凭据匹配&#xff0c;则用户身份验证成功&#xff0c;可执行已向其授权的操作。 授权指判断允许用户执行的操作的…

flex容器属性(一)

一&#xff0c;概念 flexible box ,意为“弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性。 块级布局更侧重于垂直方向&#xff0c;行内布局更侧重于水平方向&#xff0c;于此相对的&#xff0c;弹性盒子布局算法是方向无关的。 块级flex布局&#xff1a; .box{display…