【云圖說】第235期 DDS讀寫兩步走 帶您領(lǐng)略只讀節(jié)點的風采
1914
2022-05-30
終于快來到框架解析的結(jié)尾了,本文將會帶領(lǐng)大家領(lǐng)略框架中的模型以及視圖的奧秘。
ThinkPHP模型以及視圖層深度解析
前言
一、Db操作類和其它類對應(yīng)關(guān)系解刨
二、Db類庫場景分析
三、Db類庫巧妙結(jié)合連接器、查詢器、sql生成器使用
四、關(guān)于getLastSql的實現(xiàn)過程
五、總結(jié)
前言
在日常開發(fā)過程中模型的使用是非常之多的,但是在開發(fā)過程只知道如何使用,并不知道內(nèi)在是如何實現(xiàn)的,模型是不管接口還是后臺都會使用到的東西。
關(guān)于視圖在前后臺分離的大趨勢下,框架存在視圖大多數(shù)還是針對于后臺開發(fā)的使用。
本文也是對框架解讀快到最后階段了,接下來咔咔將帶領(lǐng)大家一起學習關(guān)于在框架中Db類的奧秘。
下圖為咔咔提供的腦圖可以根據(jù)這個腦圖進行閱讀文章。
一、Db操作類和其它類對應(yīng)關(guān)系解刨
在學習模型之前一定要知道的就是DB這個類,這個類也是對數(shù)據(jù)庫的操作。
在框架中存在這樣一個配置文件,在這個配置文件里邊會存在關(guān)于數(shù)據(jù)庫配置的一系列信息。
在接下來的過程中咔咔也會簡單的創(chuàng)建一個數(shù)據(jù)庫來做演示。
同樣在框架的核心層存在倆個類,分別為Db類和Model類,這倆個類就是接下來的解析對象。
在解析Db操作類和其它類對應(yīng)關(guān)系解刨之前,我們先創(chuàng)建一個數(shù)據(jù)庫作為演示使用。
首先先來看一下Db類的信息。
通過上圖我們可以看到關(guān)于Db類的一部分信息,就是使用Db類的一些查詢方法。
但是來到Db類的最后可以看到一個熟悉的方法__callStatic。
這個方法在一直讀咔咔文章的讀者應(yīng)該已經(jīng)很是熟悉了,這個方法在門面源碼解析那一節(jié)中進行過深入的了解。
對于這個方法只需要記住的是在調(diào)用沒有聲明的靜態(tài)方法時會進行調(diào)用。
至于call_user_func_array這個函數(shù)的使用可以理解為,這個方法是內(nèi)置函數(shù),可以直接調(diào)用函數(shù)運行,也就是可以直接運行方法。
在通過剛剛的查看Db類的注釋信息時可以看到Db類是使用著Connection這個類,也就是連接數(shù)據(jù)庫類。
進入到這個類里邊簡單的看一下構(gòu)造函數(shù)即可,至于是怎么一個運行順序會在下文進行講解。
在框架中操作控制器有倆大場景,第一中為Db類操作,第二種就是Model操作。
其中Connection·為連接器,Query為查詢器,Builder為sql生成器,exception為異常類。
知道了以上的幾個信息,在接下來的理解過程中會有一定的幫助,在下一節(jié)中將會對Db類庫場景分析。
二、Db類庫場景分析
先從一個簡單的案例進行解析,先來看一下數(shù)據(jù)庫的數(shù)據(jù)。
然后來到控制器寫一個簡單的查詢案例,在創(chuàng)建控制器之前先使用命令進行創(chuàng)建一個測試控制器。
在這個控制器進行簡單的查詢數(shù)據(jù)。
查詢結(jié)果如下
在這個案例中,可以看到使用的是Db::query這種查詢方式,接下來對于這種查詢方式進行簡單的剖析。
接著執(zhí)行流程就會來到Db這個類,在這個類中可以看到關(guān)于當對象訪問不存在的靜態(tài)方法時,__callStatic()方法會被自動調(diào)用。
這個方法在之前門面的講解中進行了深度講解。
從上圖可以看到當執(zhí)行訪問對象不存在的靜態(tài)方法時會執(zhí)行到call_user_func_array調(diào)用回調(diào)函數(shù),并把一個數(shù)組參數(shù)作為回調(diào)函數(shù)的參數(shù)
接著代碼就會執(zhí)行到static::connect()這行代碼,由于本類Db沒有繼承任何的類,所以對于static這個的使用就是調(diào)用本類。
如果當Db類繼承了其它類那么就會有一定的區(qū)別,這個區(qū)別就是關(guān)于static關(guān)鍵字,給大家做的一點點冷門知識得補充,當一個類繼承一個類時,在父類實用static關(guān)鍵字時,默認調(diào)用的子類的方法。
切換數(shù)據(jù)庫連接
因為沒有任何繼承,所以會來到本類的connect這個方法。
在這個類里邊首先會返回結(jié)果為數(shù)據(jù)庫配置信息。
然后會從配置信息中獲取到query這個索引,最終返回\think\db\Query這個字符串,這里一定要注意返回的是字符串,不是這個類的實例。
緊接著就會執(zhí)行到第三步創(chuàng)建數(shù)據(jù)庫連接對象實例,接下里將會對這一步進行解析。
緊接著會來到文件實際執(zhí)行的為 new \think\db\Query,最終會返回執(zhí)行查詢 返回數(shù)據(jù)集,返回數(shù)據(jù)為返回 object(think\db\Query)
關(guān)于這個$this->connection是在本類的構(gòu)造函數(shù)進行設(shè)置的。
先簡單的看一下這個構(gòu)造函數(shù),在這個構(gòu)造函數(shù)中直接就設(shè)置了connection這個屬性的值,所以在上圖中可以使用。
在這里執(zhí)行完成之后就會將返回的值給從一開始就解析的這個調(diào)用未聲明的靜態(tài)方法會進行調(diào)用。
其中static::connect()就是最終返回的值static::connect() 返回 object(think\db\Query)。
所以接下來代碼會執(zhí)行到 thinkphp/library/think/db/Query.php 的 query方法
$sql 就是在Db::query()中傳遞的sql語句,并且執(zhí)行查詢 返回數(shù)據(jù)集
最后這段代碼會執(zhí)行think\db\connector\Mysql的query方法
接下來來到think\db\connector\Mysql的query方法
在這個方法中主要做了三件事情。
$this->initConnect 初始化數(shù)據(jù)庫連接
$this->PDOStatement->execute(); 執(zhí)行查詢
return $this->getResult($pdo, $procedure); 返回結(jié)果集
解析$this->initConnect 初始化數(shù)據(jù)庫連接
在這個方法中可以看到是進行了一次配置信息獲取,首先需要明白這個配置信息是什么。
這個配置項是在配置文件database中配置的,根據(jù)注釋提供的信息可以看到主要是關(guān)于主從服務(wù)器設(shè)置的。
一般情況下是不會在框架中配置主從信息的,這里就不去解析框架是如何實現(xiàn)數(shù)據(jù)庫的主從配置了。
在這個判斷中在進行了一次判斷當前數(shù)據(jù)庫連接的id,然后執(zhí)行了連接數(shù)據(jù)庫方法。
這個方法最終會返回object(PDO)#33的一個實例信息。
$this->PDOStatement->execute(); 執(zhí)行查詢
第二件事情做的就是執(zhí)行查詢,接下來我們來詳細說明一下這個到底是如何執(zhí)行的。
在返回pdo實例時,將這個實例賦值給了$this->PDOStatement這個屬性,所以會去PDO類中進行執(zhí)行。
在這里大家需要明白一件事情就是關(guān)于execute這個方法,用于執(zhí)行返回多個結(jié)果集、多個更新計數(shù)或二者組合的語句。
第三件事情返回結(jié)果集
直到這里就是執(zhí)行的最后一步就是返回結(jié)果集。
這里使用的方法都是查詢底層,就在去解析了,在這里就會返回最終查詢結(jié)果。
最終結(jié)果就會返回給這里__callStatic方法,并且返回給上層的$res變量。
直到這里關(guān)于使用Db查詢的執(zhí)行流程就解析完了, 但是框架給封裝的方法不僅僅只有query,其它的查詢方式可以按照咔咔的這個流程在進行簡單的分析。
最后執(zhí)行的都會是這一節(jié)的最后幾個流程,只是前邊執(zhí)行會有一點點區(qū)別而已。
三、Db類庫巧妙結(jié)合連接器、查詢器、sql生成器使用
在上目錄中咔咔使用了query作為案例演示,這個使用在框架中是不建議使用的,因為在維護的方面會有一定的難度。
本節(jié)案例將會使用框架常用的查詢數(shù)據(jù)庫方式進行查詢。
在上圖中可以看到使用了平時最常用的查詢方式,接下來將會對這組案例進行詳細分析。
同樣代碼會來到Db類的__callStatic這個方法,這個方法就是在調(diào)用沒有聲明的靜態(tài)方法會進行執(zhí)行的。
這個方法跟__call方法是有區(qū)別的,__call方法是調(diào)用不存在的方法會進行調(diào)用,一定要注意倆者的區(qū)別。
對于上圖方法中static::connect()執(zhí)行最后會返回 object(think\db\Query)這個對象,至于內(nèi)部流程的執(zhí)行可以參考第二目錄的內(nèi)容。
所以執(zhí)行流程會來到thinkphp/library/think/db/Query.php這個類的table方法。
參數(shù)就是table中傳遞的數(shù)據(jù)庫表名tp_test。
按照上圖提供的代碼會對傳遞過來的表名進行三次判斷。
第一次判斷是否為字符串
第二次判斷是否存在 )
第三次判斷是否存在 ,
根據(jù)傳遞過來的字符串以上三個判斷均不成立,于是會執(zhí)行到下面流程。
在table這個方法中可以看到最后的執(zhí)行流程就是將傳遞過來的表名存放在屬性options這個里邊。
并且最后會將think\db\Query Object這個對象進行返回。
where方法解析
table方法分析完成后會緊接著執(zhí)行where方法,同樣還是在類thinkphp/library/think/db/Query.php
上圖中在這個類中可以看到一個方法func_get_args,這個方法會返回一個包含函數(shù)參數(shù)列表的數(shù)組。
這個方法平時都是跟call_user_func_array同時使用,之前咔咔也使用這倆個方法進行過一次案例實驗。
然后會使用函數(shù)array_shift刪除數(shù)組中的第一個元素(red),并返回被刪除元素的值。
下圖第一個結(jié)果為func_get_args這個方法獲取出來的數(shù)據(jù),第二組結(jié)果為array_shift這個方法返回的結(jié)果。
倆組結(jié)果返回的值可以進行對比一下,可以更好的理解array_shift的使用場景。
緊接著會進行分析查詢表達式,也就是方法parseWhereExp做的事情。
在這個方法中需要注意一個點就是關(guān)于傳遞過來的這倆個參數(shù)。
參數(shù)一為查詢邏輯,參數(shù)二就是在使用案例時傳入的參數(shù)。
在代碼的第一行就需要我們來學習的一個知識點instanceof。
instanceof可以判斷某個對象是否是某個類的實例,判斷一個對象是否實現(xiàn)了某個接口。
關(guān)于這個的使用案例在文章ThinkPHP源碼解析之控制器這一文中做了詳細的說明。
根據(jù)學習instanceof的作用可以清晰的明白第一個判斷不會進行執(zhí)行。
在繼續(xù)學習以下的執(zhí)行流程,根據(jù)咔咔圈出來的框來進行對代碼進行簡單的解析。
根據(jù)上圖首先會對查詢邏輯的符號全部轉(zhuǎn)為小寫
然后在進行判斷$field instanceof Where傳遞過來的參數(shù)是否為Where類的實例。
最后一個判斷就是$field instanceof Expression跟上一步是判斷同樣的功能。
所以說代碼最終的執(zhí)行邏輯就是下圖圈到的部分。
還記得在案例過程中給where傳遞的參數(shù)就是一個數(shù)組。
如果將參數(shù)改為where('t_id',1)則就會走is_string($field)的這個流程,這個流程就交給大家了,咔咔就不去解析。
這里咔咔還是使用數(shù)組作為參數(shù)進行解析,那么代碼依然會執(zhí)行本類的parseArrayWhereItems這個方法
在這個方法中先需要知道key會返回什么,從當前內(nèi)部指針位置返回元素鍵名。
所以代碼會去執(zhí)行if語句的判斷,根據(jù)上邊的所有判斷都不符合所以會執(zhí)行這段代碼$where[] = [$key, is_array($val) ? 'IN' : '=', $val];
這段代碼會判斷循環(huán)數(shù)組的value值是否為數(shù)組,如果為數(shù)組就是in,反之為=,由于value為1所以數(shù)組的第二個值為=。
那么最終where的值就是下圖打印的數(shù)據(jù)。
由于where不為空,代碼執(zhí)行流程會執(zhí)行到下圖位置,最終在返回本類實例。
find()執(zhí)行流程
接著代碼會還是執(zhí)行本類的find方法,查找單條記錄。
由于find中是沒有傳遞參數(shù)的,所以代碼會執(zhí)行到$this->parseOptions();分析表達式(可用于查詢或者寫入操作)
就目前寫的案例而言,這段看似很長的代碼大家好好看看都可以看明白,最終依然是返回當前的所有參數(shù)。
以下就是返回的所以結(jié)果
真正的查詢數(shù)據(jù)是這塊代碼$result = $this->connection->find($this);,這段代碼會執(zhí)行到文件thinkphp/library/think/db/Connection.php
從這塊代碼可以看到當查詢一條數(shù)據(jù)時框架默認給加上了limit為1,至于為什么這么加你就需要查看一下sql優(yōu)化方面的知識了。
在這里就是關(guān)于sql語句的生成,代碼自己好好看看就會明白,咔咔解析的只是執(zhí)行流程和具體代碼簡單的了解一下即可。
至于具體實現(xiàn)流程咔咔在后期如果有機會會單獨把每個方法進行深度解析,那時就是主要針對代碼的解析。
最終返回結(jié)果如下
以上就是關(guān)于Db在結(jié)合連接器,查詢器,生成器實現(xiàn)的數(shù)據(jù)庫查詢功能。
截止到這里關(guān)于Db的場景就分析到這里,接下來咔咔將會對Model進行簡單的分析。
四、關(guān)于getLastSql的實現(xiàn)過程
還是之前的案例,我們來使用這個方法打印一下結(jié)果來看一下是什么。
看到上圖就知道是框架最終給生成的SQL語句,那么接下來咔咔就會帶大家一起來探討一下,這個sql語句是如何生成的。
下圖為本次演示的案例,也就是咔咔下圖圈出來的地方。
從上圖圈出來的地方進行代碼追蹤會到文件thinkphp/library/think/Db.php,并且會去執(zhí)行本類的__callStatic方法,這個方法就不在進行解釋了,在上文和之前也已經(jīng)提到過多次了。
并且返回結(jié)果也不去做聲明了,上文也提到了,這里只需要知道最終返回結(jié)果為返回 object(think\db\Query)
根據(jù)上圖的返回結(jié)果可以知道最終回去調(diào)用object(think\db\Query)這個類的getLastSql這個方法
根據(jù)這個方法可以知道是獲取最近一次查詢的sql語句。
這里就會有點疑問了,關(guān)于屬性connection到底是什么,這里在進行一次簡單的簡析。
關(guān)于這種屬性的聲明一般都會在本類的構(gòu)造函數(shù)或者父類的構(gòu)造函數(shù)中進行聲明,這也是在閱讀源碼時的一個小竅門。
于是我們首先就需要來到本類的構(gòu)造函數(shù)來看一眼。
通過上圖可以得知,此處使用了依賴注入的方式,所以Connection就是一個對象,并且也是框架中所謂的連接器。
所以說這個Connection對象就是下圖打印出來的。
根據(jù)上圖得知使用的類文件應(yīng)該就是think\db\connector\Mysql那么就會執(zhí)行這個類里邊的getLastSql方法。
但是來到這個類執(zhí)行你會發(fā)現(xiàn)這個類里邊根本是沒有這個方法。
根據(jù)上圖的繼承關(guān)系,我們就知道這個方法是在thinkphp/library/think/db/Connection.php這個類文件里邊。
下圖即是這個方法執(zhí)行過程,可以看到存在倆個參數(shù),但是這倆個參數(shù)還是一頭霧水根本不知道是什么。
根據(jù)代碼追蹤我們對上圖所出現(xiàn)的倆個參數(shù)先進行簡單的說明
$this->queryStr當前SQL指令
$this->bind 綁定參數(shù)
追蹤$this->queryStr這個屬性值
走到這里估計有點蒙了吧!對于這個值有點確定不了了,指定不是靠打印可以獲取到結(jié)果的。
當然還有另一種辦法就是進行debug來斷點調(diào)試。
但是既然咔咔帶大家看源碼呢!就不會用上邊的倆種方式,會直接從源碼中找到蛛絲馬跡。
根據(jù)咔咔上邊給提供的案例,執(zhí)行的最后一步就是find方法,這個方法也是在thinkphp/library/think/db/Connection.php這個類里邊,尋找單條記錄。
那么我們就在這個方法中進行一點點的尋找,這里咔咔已經(jīng)給大家圈好了,就是下圖咔咔圈其起來的地方。
根據(jù)上圖咔咔給的代碼注釋,第一個參數(shù)就是生成的SQL語句,來繼續(xù)追蹤這個方法看一下,此時這個方法依然會在本類thinkphp/library/think/db/Connection.php這個文件中實現(xiàn)query方法。
在這個方法中一眼就可以看見對于這個queryStr屬性的設(shè)置,是直接給這個屬性賦值,那么也就是說這個屬性的值就是上一個SQL語句生成的SQL語句。
所以說這個getLastSql獲取的就是在這個語句之前執(zhí)行的SQL語句,也只能獲取出最近執(zhí)行的那個SQL語句。
以上就是關(guān)于getLastSql的實現(xiàn)原理,在這里需要注意的就是關(guān)于SQL的生成,這塊屬實有點復(fù)雜。
五、總結(jié)
截止到這里關(guān)于數(shù)據(jù)庫中Db類的操作場景分析以及關(guān)于結(jié)合連接器,查詢器,生成器就到這里結(jié)束了。
這里咔咔主要就是使用了倆種案例來進行執(zhí)行,第一種為原生案例,第二種為框架封裝的案例。
使用了這倆種案例來對源碼進行了深度解析,但是在文檔還有很多的實現(xiàn)方法,其它的方法只需要根據(jù)咔咔給的提示然后一點點解析即可。
不需要對所有的方法都進行執(zhí)行,不管任何方法走的都是上文分析的方法,也是很簡單。
最后在演示了一下關(guān)于使用getLastSql來獲取最后一次執(zhí)行的SQL語句查詢,這里的實現(xiàn)原理主要就是在Db類操作數(shù)據(jù)庫時,不管是使用find方法還是select方法最終都會走向一個方法那就是query方法。
同樣在這個方法中存在一個屬性值queryStr,也就是在這個時候?qū)QL語句賦值進去的,然后在使用getLastSql這個方法使用queryStr和bind屬性在對SQL進行拼接,最總返回SQL語句。
堅持學習、堅持寫博、堅持分享是咔咔從業(yè)以來一直所秉持的信念。希望在偌大互聯(lián)網(wǎng)中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。
ThinkPHP 數(shù)據(jù)庫
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應(yīng)法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。