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()獲取查詢結果,固完整的類圖表示如下:
thinkphp5源碼解析(1)數據庫