where('..."/>
溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

thinkphp5源碼解析(1)數(shù)據(jù)庫

發(fā)布時間:2020-07-18 22:12:17 來源:網(wǎng)絡 閱讀:896 作者:zhupp1988 欄目:web開發(fā)

前言

tp5的數(shù)據(jù)庫操作全部通過Db類完成,比較符合國人的習慣,比如簡單的Db::query()、Db::execute(),還有復雜的鏈式操作Db::table('user')->where('id=1')->select(),下面就通過源碼來了解其工作流程

看代碼之前,先看看涉及到的類都有哪些,tp5的數(shù)據(jù)庫相關的類有以下幾個:

  • Db(用戶接口)

  • Connection(連接器)

  • Query(查詢器)

  • Builder(SQL生成器)

Db::query()發(fā)生了什么? 

假定配置文件設置驅(qū)動為Mysql,當執(zhí)行以下代碼時,tp5的數(shù)據(jù)庫類是怎么工作的?
Db::query("select * from user where id=?", [1]);

為了節(jié)省篇章以及更好地理解流程,下面只展示核心代碼,部分代碼被簡化或改造,我們來看看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(),所以觸發(fā)了__callStatic(),__callStatic()又調(diào)用自身的connect(),connect()實例化Mysql連接器(傳入數(shù)據(jù)庫配置$options),然后保存到$instance(數(shù)據(jù)庫連接實例數(shù)組),再來看看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();
        
    }
    
}
結(jié)論

Db::query()觸發(fā)Db::__callStatic(),實例化Mysql連接器并調(diào)用Mysql->query(),而Mysql連接器繼承了Connection,所以實際上是調(diào)用了Connection->query()

thinkphp5源碼解析(1)數(shù)據(jù)庫

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

Db和Mysql連接器都沒有定義table()方法,發(fā)現(xiàn)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')實際上是觸發(fā)了__call()魔術方法,然后實例化了一個Query對象(構(gòu)造函數(shù)傳入當前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);
    }

}

首先構(gòu)造函數(shù)保存了當前的Mysql連接器對象,并實例化think\db\builder\Mysql

Query->table()把表名保存到$options數(shù)組,然后返回$this(當前實例)從而實現(xiàn)鏈式操作,where()同樣,重點看看select(),它拿到$options之后把它清空以便下次使用,然后調(diào)用了Builder->select()拿到拼裝好的sql,交由Connection->query()查詢數(shù)據(jù)庫獲得結(jié)果集,整個流程到此結(jié)束,那么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

結(jié)論

Db::table()觸發(fā)了__callStatic()實例化Connection并調(diào)用table(),由于Connection也沒有定義table(),又觸發(fā)了自身的__call()實例化Query并調(diào)用table(),table()返回$this實現(xiàn)鏈式操作DB::table()->where()->select(),而select又調(diào)用Builder->select()拿到sql,最終調(diào)用Connection->query()獲取查詢結(jié)果,固完整的類圖表示如下:

thinkphp5源碼解析(1)數(shù)據(jù)庫


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI