前言

tp5的数据库操作全部通过Db类完成,比较符合国人的习惯,比如简单的Db::query()Db::execute(),还有复杂的链式操作Db::table('user')->where('id=1')->select(),下面就通过源码来了解其工作流程

看代码之前,先看看涉及到的类都有哪些,tp5的数据库相关的类有以下几个:

  • Db(用户接口)
  • Connection(连接器)
  • Query(查询器)
  • Builder(SQL生成器)

Db::query()发生了什么?

假定配置文件设置驱动为Mysql,当执行以下代码时,tp5的数据库类是怎么工作的?

Db::query("select * from user where id=?", [1]);

为了节省篇章以及更好地理解流程,下面只展示核心代码,部分代码被简化或改造,我们来看看Db类:


class Db
{
private static $instance = []; private static function parseConfig($config)
{
if (empty($config)) {
$config = Config::get('database');
} else {
$config = Config::get($config);
}
return $config;
} public static function connect($config = [])
{
$name = md5(serialize($config));
if (!isset(self::$instance[$name])) {
$options = self::parseConfig($config);
self::$instance[$name] = new \think\db\connector\Mysql($options);
}
return self::$instance[$name];
} public static function __callStatic($method, $params)
{
return call_user_func_array([self::connect(), $method], $params);
} }

因为Db类没有定义query(),所以触发了__callStatic(),__callStatic()又调用自身的connect(),connect()实例化Mysql连接器(传入数据库配置$options),然后保存到$instance(数据库连接实例数组),再来看看Mysql连接器:


namespace think\db\connector;
class Mysql extends Connection
{ protected $builder = '\\think\\db\\builder\\Mysql'; }

Mysql连接器也没有定义query()呀,它继承了Connection,看看Connection有没有:


abstract class Connection
{ protected $PDOStatement;
protected $linkID;
protected $config = []; public function __construct(array $config = [])
{
if (!empty($config)) {
$this->config = array_merge($this->config, $config);
}
} protected function getResult()
{
return $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
} protected function bindValue(array $bind = [])
{
foreach ($bind as $key => $val) {
$param = is_numeric($key) ? $key + 1 : ':' . $key;
if (is_array($val)) {
if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
$val[0] = 0;
}
$result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
} else {
$result = $this->PDOStatement->bindValue($param, $val);
} }
} public function connect()
{
if (!$this->linkID) {
$config = $this->config;
$this->linkID = new PDO($config['dsn'], $config['username'], $config['password']);
}
return $this->linkID;
} public function query($sql, $bind = [])
{
$this->connect(); if (empty($this->PDOStatement)) {
$this->PDOStatement = $this->linkID->prepare($sql);
} $this->bindValue($bind);
$this->PDOStatement->execute();
return $this->getResult(); } }

结论

Db::query()触发Db::__callStatic(),实例化Mysql连接器并调用Mysql->query(),而Mysql连接器继承了Connection,所以实际上是调用了Connection->query()

Db::table('user')->where('id=1')->select()发生了什么?

Db和Mysql连接器都没有定义table()方法,发现Connection也有个__call():


protected function getQuery()
{
return new \think\db\Query($this);
} public function __call($method, $args)
{
return call_user_func_array([$this->getQuery(), $method], $args);
}

所以Db::table('user')实际上是触发了__call()魔术方法,然后实例化了一个Query对象(构造函数传入当前Mysql连接器对象),看看Query里面做了什么:


namespace think\db;
class Query
{
protected $connection;
protected $builder; public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->setBuilder();
} protected function setBuilder()
{
$this->builder = new \think\db\builder\Mysql($this->connection, $this);
} public function table($table)
{
$this->options['table'] = $table;
return $this;
} public function where($where)
{
$this->options['where'] = $where;
return $this;
} public function query($sql)
{
return $this->connection->query($sql);
} public function select()
{
$options = $this->options;
$this->options = [];
$sql = $this->builder->select($options);
return $this->query($sql);
} }

首先构造函数保存了当前的Mysql连接器对象,并实例化think\db\builder\Mysql

Query->table()把表名保存到$options数组,然后返回$this(当前实例)从而实现链式操作,where()同样,重点看看select(),它拿到$options之后把它清空以便下次使用,然后调用了Builder->select()拿到拼装好的sql,交由Connection->query()查询数据库获得结果集,整个流程到此结束,那么Builder是怎么拼装sql的呢?


namespace think\db\builder;
class Mysql extends Builder
{
protected function parseRand()
{
return 'rand()';
}
}

think\db\builder\Mysql并没有定义select(),不过它继承了Builder,看看Builder代码:


namespace think\db;
abstract class Builder
{
protected $connection;
protected $query;
protected $selectSql = 'SELECT %FIELD% FROM %TABLE% %WHERE%'; public function select($options = [])
{
$sql = str_replace(
['%TABLE%', '%FIELD%', '%WHERE%'],
[
$options['table'],
$options['field'] ?: '*',
$options['where'] ? 'WHERE'.$options['where'] : '',
], $this->selectSql);
return $sql;
} }

Builder通过$options替换sql模板拿到sql

结论

Db::table()触发了__callStatic()实例化Connection并调用table(),由于Connection也没有定义table(),又触发了自身的__call()实例化Query并调用table(),table()返回$this实现链式操作DB::table()->where()->select(),而select又调用Builder->select()拿到sql,最终调用Connection->query()获取查询结果,固完整的类图表示如下:

thinkphp5源码解析(1)数据库的更多相关文章

  1. thinkphp5源码解析(2)控制器

    入口文件index.php: // 定义应用目录 define('APP_PATH', __DIR__ . '/../application/'); // 加载框架引导文件 require __DIR ...

  2. Ocelot简易教程(七)之配置文件数据库存储插件源码解析

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html 上篇文章给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储 ...

  3. Android 数据库 ObjectBox 源码解析

    一.ObjectBox 是什么? greenrobot 团队(现有 EventBus.greenDAO 等开源产品)推出的又一数据库开源产品,主打移动设备.支持跨平台,最大的优点是速度快.操作简洁,目 ...

  4. Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

    Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的?   如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...

  5. 时序数据库 Apache-IoTDB 源码解析之文件数据块(四)

    上一章聊到行式存储.列式存储的基本概念,并介绍了 TsFile 是如何存储数据以及基本概念.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件格式简介(三) 打一波广告,欢迎大家访问Io ...

  6. 时序数据库 Apache-IoTDB 源码解析之文件格式简介(三)

    上一章聊到在车联网或物联网中对数据库的需求,以及 IoTDB 的整体架构,详情请见: 时序数据库 Apache-IoTDB 源码解析之系统架构(二) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 ...

  7. 时序数据库 Apache-IoTDB 源码解析之系统架构(二)

    上一章聊到时序数据是什么样,物联网行业中的时序数据的特点:存量数据大.新增数据多(采集频率高.设备量多).详情请见: 时序数据库 Apache-IoTDB 源码解析之前言(一) 打一波广告,欢迎大家访 ...

  8. 时序数据库 Apache-IoTDB 源码解析之文件索引块(五)

    上一章聊到 TsFile 的文件组成,以及数据块的详细介绍.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件数据块(四) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star. ...

  9. 时序数据库 Apache-IoTDB 源码解析之元数据索引块(六)

    上一章聊到 TsFile 索引块的详细介绍,以及一个查询所经过的步骤.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件索引块(五) 打一波广告,欢迎大家访问 IoTDB 仓库,求一波 ...

随机推荐

  1. 机器翻译评测——BLEU算法详解

    ◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/7679284.html 前言 近年来,在自然语言研究领域中, ...

  2. JMeter 压力测试使用CSV参数

    表示之前从没用过JMeter所以记录一下使用过程 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测 ...

  3. 移动端二三事【三】:transform的注意事项

    1.js操作transition时需使用驼峰命名: div.style.WebkitTransform = div.style.transform = "rotate(90deg)" ...

  4. JavaScript中把Json字符串转化为对象

    1.采用eval()函数 定义和用法 eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码. 语法 eval(string) 参数 描述 string 必需.要计算的字符串, ...

  5. Photoshop 学习中

    快捷键: f8打开信息调板,注意虽然数字最高是255,但0也是数值之一,因此共256级. f7开启图层调板 f6调出调色板,按D还原为默认颜色 ctrl + 放大,ctrl - 缩小 f 可以切换显示 ...

  6. replace 使用函数作为第二参数

    var sToChange = “The sky is red.”;var reRed = /red/;var sResultText = sToChange.replace(reRed, funct ...

  7. form提交地址地址正确但是依旧报错404找不到路径

    ---恢复内容开始--- 我的jsp中保含了">="和"<="符号,form提交的时候会有个标签校验,如下: private static bool ...

  8. 添加无登录权限的SSH用户命令

    useradd -M -s /sbin/nologin -n username  passwd username userdel -r username

  9. stack 的入门

    #include "iostream"#include "stack" using namespace std; void main12(){ stack &l ...

  10. 一脚踏进Memcached的大门

    Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...