thinkphp5源码解析(1)数据库

前言

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()获取查询结果,固完整的类图表示如下:

时间: 2024-11-29 11:48:29

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

Android xUtils3源码解析之数据库模块

xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3源码解析之图片模块 三. Android xUtils3源码解析之注解模块 四. Android xUtils3源码解析之数据库模块 配置数据库 DbManager.DaoConfig daoConfig = new DbManager.DaoConfig() .setDbName("test.db") .setDbVersion(1) .setDbOpenListe

Android xUtils3源码解析之注解模块

xUtils3源码解析系列 一. Android xUtils3源码解析之网络模块 二. Android xUtils3源码解析之图片模块 三. Android xUtils3源码解析之注解模块 四. Android xUtils3源码解析之数据库模块 初始化 public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { su

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

Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的? ??如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一样,本篇文章最最核心的要点就是 SqlSession实现数据库操作的源码解析.但按照惯例,我这边依然列出如下的问题: 1. SqlSession 是如何被创建的? 每次的数据库操作都会创建一个新的SqlSession么?(也许有很多同学会说SqlSession是通过 SqlSessionFactor

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

上一章聊到行式存储.列式存储的基本概念,并介绍了 TsFile 是如何存储数据以及基本概念.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件格式简介(三) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star .欢迎关注头条号:列炮缓开局,欢迎关注OSCHINA博客 这一章主要想聊一聊: TsFile的文件概览 TsFile的数据块 TsFile文件概览 一个完整的 TsFile 是由图中的几大块组成,图中的数据块与索引块之间使用 1 个字节的分隔符 2 来进行分隔,这个分

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

上一章聊到 TsFile 的文件组成,以及数据块的详细介绍.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件数据块(四) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star. 这一章主要想聊聊: TsFile索引块的组成 索引块的查询过程 索引块目前在做的改进项 索引块 索引块由两大部分组成,其写入的方式是从左到右写入,也就是从文件头向文件尾写入.但读出的方式是先读出TsFileMetaData 再读出 TsDeviceMetaDataList 中的具体一部分.我们按照读

消息中间件 RocketMQ源码解析:事务消息

关注微信公众号:[芋艿的后端小屋]有福利: RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表 RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址 您对于源码的疑问每条留言都将得到认真回复.甚至不知道如何读源码也可以请教噢. 新的源码解析文章实时收到通知.每周更新一篇左右. 1. 概述 2. 事务消息发送 2.1 Producer 发送事务消息 2.2 Broker 处理结束事务请求 2.3 Broker 生成

Python2 基本数据结构源码解析

Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. PyStringObject 0x05. PyListObject 0x06. PyDictObject 0x07. PyLongObject 0x00. Preface 一切皆对象,这是Python很重要的一个思想之一,虽然在语法解析上有些细节还是不够完全对象化,但在底层源码里,这个思想还是贯穿

Java集合---Array类源码解析

Java集合---Array类源码解析              ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型:采用改进的归并排序. 1.对于基本类型源码分析如下(以int[]为例): Java对Primitive(int,float等原型数据)数组采用快速排序,对Object对象数组采用归并排序.对这一区别,sun在

Android xUtils3源码解析之图片模块

初始化 x.Ext.init(this); public static void init(Application app) { TaskControllerImpl.registerInstance(); if (Ext.app == null) { Ext.app = app; } } public final class TaskControllerImpl implements TaskController { public static void registerInstance()