ThinkPhp 源码解读 Model篇
ThinkPhp 源碼解讀 Model篇
本篇主要講解TP下 Model Query Connection DB Builder 類的關系
最終理解 model(‘xxx’)->startTrans(); model(‘yyy’)->conmmit(); 事物流程的解析
問題:
1:TP 是如何管理不同的模型對象的,調用modle()所做的事情
2:TP 是如何管理數據庫連接的,為何 不同的model 調用能保證事物統一
3:TP model 里的鏈式調用時如何實現的?
開始 :從 model 方法進去:
public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common'){$guid = $name . $layer;if (isset(self::$instance[$guid])) {return self::$instance[$guid];}if (false !== strpos($name, '\\')) {$class = $name;$module = Request::instance()->module();} else {if (strpos($name, '/')) {list($module, $name) = explode('/', $name, 2);} else {$module = Request::instance()->module();}$class = self::parseClass($module, $layer, $name, $appendSuffix);}if (class_exists($class)) {$model = new $class();} else {$class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);if (class_exists($class)) {$model = new $class();} else {throw new ClassNotFoundException('class not exists:' . $class, $class);}}self::$instance[$guid] = $model;return $model;}解析: model 方法是 Loader 類的靜態方法,主要根據全類名初始化modle類,并且以全類名為key將modle對象保存到靜態數組中,所以TP 中的model對象都是單例存在
接下來看 Model類
abstract class Model implements \JsonSerializable, \ArrayAccess{//數據庫查詢對象池 /db/Query類對象 以model類名為key的單例,即每個model子類都有對應的一個query類統一保存在 Model 父類里protected static $links = [];// 數據庫配置, 這不是connection類對象,其實這就是數據庫配置數組,類似配置里的database 數組!這命名也是坑protected $connection = [];// 數據庫查詢對象, 其實這是數據庫查詢對象的全類名! 類似 \\think\\db\\Query 不是對象!protected $query;.....解析: Model是抽象類,跟數據庫連接相關的成員屬性 為上面3個,屬性上面也有解析,接下來看 modle 類做了什么事,主要方法 __construct->getQuery->buildQuery
通過這3方法完成對當前class Query對象的創建并保存到 links 全局數組中
下面看看 為什么Model 類里沒有我們常用的 where->find 等方法,但在子類里又可以調用?
調用沒有的方法首先觸發的就是_call 魔術方法 進去看看
public function __call($method, $args){$query = $this->db(true, false);if (method_exists($this, 'scope' . $method)) {// 動態調用命名范圍$method = 'scope' . $method;array_unshift($args, $query);call_user_func_array([$this, $method], $args);return $this;} else {return call_user_func_array([$query, $method], $args);}}很明顯如果類里沒有對應方法 或者沒有對應的scope 方法 則會調用query 里對應的方法,(所以 寫一手別人看不懂的代碼是多裝逼的事)這里的query 通過 this->db 方法獲取,其實就是拿的靜態數組links 里面的!
modle 類解決,我們繼續看Query 類 有什么東西
class Query{// 數據庫Connection對象實例,這個才是真的Connection 類的對象,一般從DB類里直接獲取,后面有簡單分析DB類protected $connection;// 數據庫Builder類對象實例, builder類即 '\\think\\db\\builder\\'里面的類,邏輯功能比較單一, 主要解析各種數組類參數 生成字符串sql 語句protected $builder;// 當前模型類名稱protected $model;....解析:
Query作用 主要封裝數據庫查詢實例connection 以及鏈式調用方法(where->find ..),通過builder 對參數進行解析獲得sql 并執行,
看構造方法:
這里的connection 通過 Db::connect 靜態方法獲得,我們看 DB 類方法
public static function connect($config = [], $name = false){if (false === $name) {$name = md5(serialize($config));}if (true === $name || !isset(self::$instance[$name])) {// 解析連接參數 支持數組和字符串$options = self::parseConfig($config);if (empty($options['type'])) {throw new \InvalidArgumentException('Undefined db type');}$class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']);// 記錄初始化信息if (App::$debug) {Log::record('[ DB ] INIT ' . $options['type'], 'info');}if (true === $name) {return new $class($options);} else {self::$instance[$name] = new $class($options);}}return self::$instance[$name];}看到這里終于看懂,TP 里所有Query 對象的connection 都是以數據庫配置MD5值為key 的單例,即不改變數據庫鏈接配置,不管我們創建多少model,其實用的都是同一個connection!!!
繼續看connection 類
解析: connention 類 主要是封裝保存PDO實例,邏輯比較簡單,pdo實例保存在成員屬性links里,這里如果不是主從多庫的情況一般只會維護一個PDO實例,多庫情況請看multiConnect() 方法這里不展開
public function connect(array $config = [], $linkNum = 0, $autoConnection = false){if (!isset($this->links[$linkNum])) {if (!$config) {$config = $this->config;} else {$config = array_merge($this->config, $config);}// 連接參數if (isset($config['params']) && is_array($config['params'])) {$params = $config['params'] + $this->params;} else {$params = $this->params;}// 記錄當前字段屬性大小寫設置$this->attrCase = $params[PDO::ATTR_CASE];// 數據返回類型if (isset($config['result_type'])) {$this->fetchType = $config['result_type'];}try {if (empty($config['dsn'])) {$config['dsn'] = $this->parseDsn($config);}if ($config['debug']) {$startTime = microtime(true);}$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);if ($config['debug']) {// 記錄數據庫連接信息Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql');}} catch (\PDOException $e) {if ($autoConnection) {Log::record($e->getMessage(), 'error');return $this->connect($autoConnection, $linkNum);} else {throw $e;}}}return $this->links[$linkNum];}到這里 TP從 model 方法到 數據庫連接一路已經走完,下面總結幾點
1:TP model 子類會以子類全類名為key 的單例模式保存在 model 抽象父類的靜態屬性里,即model(xxx) 多次只會有一個對象,且是全局的;
2:每個model子類會持有一個Query對象,并且所有的Query對象,以model全類名為key 的單例模式保存在 model 抽象父類的靜態屬性里,
就是創建2個modle 對象,它們會持有同一個Query對象
3:TP 通過 __call 魔術方法 實現在modle里調用Query 類的鏈式方法,如 where->select 這些方法均封裝在Query類里
4:每個query 會有持有一個connection 實例,并且 所有的connection實例都會以數據庫連接配置MD5值為key 的單例形式保存在DB類的靜態屬性里,
所以,只要TP 數據庫配置沒有修改,不管你怎么創建不同的model 類,最后都會使用到同一connection實例,并且此實例是全局靜態保存的
總結:TP 對應普通web服務的應用來說是很友好的,對普通web服務PHP都是單線程,所以TP全程都是以單例為核心,
整個框架不存在多次創建對象,多次鏈接數據庫的情況,一次訪問只持有一個數據庫連接;
但 這僅是對普通web服務來說是友好的,對于像多進程的應用 如swoole 來說,TP 的這些屬性有時是致命的!后面會分析TP 跟swoole的一些坑,敬請留意
總結
以上是生活随笔為你收集整理的ThinkPhp 源码解读 Model篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JQuery的可见性选择器与show、h
- 下一篇: 百分点科技位居中国数据治理解决方案市场第