“
本文主要介紹框架的執行流程
”
前言
如果不清楚框架是怎么執行的,那么看在多的代碼都是只是認識代碼而已,閱讀源碼是為了學習其框架的設計思想和代碼模式。
而執行流程則是將我們學習的東西串聯在一起,從而更好地理解。咔咔也會給大家把執行流程用思維導圖的方式畫出來。
只要大家在本文學習到一點點的知識點,咔咔也是心滿意足的。
這個流程圖只是針對initialize的執行過程,其余的執行過程后期會進行補充,都是以腦圖的形式呈現給大家的。
一、框架執行流程之初始化應用的數據設置
這里的內容跟容器的內容有點重復,因為執行流程是從入口文件開始的,并且最后也是通過容器執行的。
然后就會進入到文件thinkphp/library/think/App.php的run方法,在這個方法中主要就是下圖框出來的地方,執行的initialize方法。
來到initialize這個方法,先看上半部分。
-
microtime(true);返回的是unix的微秒數 -
memory_get_usage返回的是分配給PHP的內存量,單位為字節 -
在接下來就是對框架的幾個路徑進行設置 -
static::setInstance($this);這里是將app這個實例設置為容器實例 -
$this->instance(‘app’, $this);這個在之前容器章節就提到了,就是為了把app這個類綁定到容器里邊去,也就是注冊樹模式。
這里有一個小的問題點給大家提出來,在初始化應用的這個方法里邊存在這樣一行代碼。
有沒有小伙伴對這個$this->env和下邊的$this->config這倆個調用有疑惑。
如果你有疑惑那就跟著咔咔一起來看,沒疑惑的就可以繼續往下看了。
App這個類是繼承的容器類,那么這個env和config不論是在app還是container類中都是沒有這倆個屬性的。
那么怎么就可以直接調用呢!而且代碼追蹤都會追蹤到env類和container類中。
需要知道這個源頭就需要我們去在大致的看一遍container類的代碼。
經過一番苦讀之后,可以看到下圖的幾行代碼。這幾行代碼全部使用的是魔術方法。
當訪問env類不存在的時候就會去執行make方法。
make這個方法在容器那一章節進行的細的不能再細的解讀了。
這個make方法最終會返回一個類的實例,并且還會存到容器里邊。
這里只放一個make方法的代碼,如果有不會的可以去看之前的文章。
最后就是加載一系列的數據,加載詳情請看前言的思維導圖。
二、如何查看一個方法都在哪里執行了
在閱讀源碼的過程中,有一個很難把控的問題就是一個方法在不同的地方進行了調用,但是咱們確一時半會根本不知道都在哪里調用了。
這里用init方法來做一個演示。
init方法是初始化應用或者模塊的一個方法,但是這里的module參數確實一個空值。
先做一個斷點查看一下相關的數據信息。
打印的結果就是空,這就是一些新學習的伙伴會犯的一個錯誤,因為這個方法不可能只調用一次的。
如果初始化模塊都是空那么這個方法就沒有存在的必要了。
那么正確的斷點方式應該是這個樣子的。
此時就會有一個問題,這個init方法明顯是被調用了倆次的,那么另一次調用的地方是在哪里呢!
如果在不知道新的技巧之前,就會進行一系列的斷點打印,看在哪里進行了執行,比如在這個init的上層去打印。
也就是在initialize那個方法里邊去打印做斷點,但是這樣很是麻煩的,而且很有可能浪費了大量的時間還是找不到正確的地方。
小技巧之debug_backtrace()
這個方法會產生一條回溯追蹤,會顯示出一個方法所有的調用位置。
使用方式就是如下圖,只需要把debug_backtrace這個方法打印出來即可。
根據得到的數據信息,就可以非常快的進行定位。
第一次就是在app類的215行。
第二次是在thinkphp/library/think/route/dispatch/Module.php類的60行
可以在這里做一個打印,看一下這個module是否為index
所以說有了這個方法就可以非??焖俚囟ㄎ徽{用位置。
三、框架執行流程之初始化應用init分析
上文給大家提供了一個小技巧debug_backtrace實戰演示了如何查看一個方法都在哪里執行的。
并且案例也是使用的init這個方法來演示的,因為接下來就是要對init這個方法進行深入的了解。
在init方法里邊主要做的事情在上邊的腦圖已經描述的很清楚了。
-
從一開始就對模塊的定位,就是在第二節中的對init方法的調用,會傳入對應的模塊 -
加載app目錄下的tags文件,在tags文件里邊就是對行為擴展定義的文件。在之前門面的文章中定義鉤子執行就在這個文件中設置的。 -
加載common文件,也就是公共文件,所以說公共文件就是在這里進行加載的。 -
加載助手函數文件helper,在助手函數里邊有一個大家特別熟悉的一個方法,那就是dump。這就是為什么在有的地方使用dump會報錯的原因。 -
加載中間件文件,這里的直接給出的是直接加載app目錄下的中間件文件,但是在框架中我們需要在定義一個目錄為http,在這個目錄下定義中間件文件。 -
注冊服務的容器對象實例,這里注冊就使用的是容器類中的bindTo方法進行綁定注冊的。 -
讀取配置文件,這段在配置文件加載那一節中已經進行深入的說明了, 這里就不提了。配置文件會讀取倆個地方一個是第一步模塊下的config文件,另一個就是config目錄下的配置文件。 -
設置模塊路徑,會把第一步獲取到的模塊進行env環境變量配置里邊 -
最后一步就是對容器中的對象實例進行配置更新,具體更新了什么在后文中給大家詳細說來。
<span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span>????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>?????*?初始化應用或模塊<br>?????*?<span class="hljs-doctag" style="color: #c678dd; line-height: 26px;">@access</span>?public<br>?????*?<span class="hljs-doctag" style="color: #c678dd; line-height: 26px;">@param</span>??string?$module?模塊名<br>?????*?<span class="hljs-doctag" style="color: #c678dd; line-height: 26px;">@return</span>?void<br>?????*/</span><br>????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">public</span>?<span class="hljs-function" style="line-height: 26px;"><span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">function</span>?<span class="hljs-title" style="color: #61aeee; line-height: 26px;">init</span><span class="hljs-params" style="line-height: 26px;">($module?=?<span class="hljs-string" style="color: #98c379; line-height: 26px;">''</span>)</span><br>????</span>{<br>????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?定位模塊目錄</span><br>????????$module?=?$module???$module?.?DIRECTORY_SEPARATOR?:?<span class="hljs-string" style="color: #98c379; line-height: 26px;">''</span>;<br>????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>?????????*?第一次:D:phpstudy_proWWWThinkPHPSourceCodeAnalysisapplication<br>?????????*?第二次:D:phpstudy_proWWWThinkPHPSourceCodeAnalysisapplicationindex<br>?????????*/</span><br>????????$path???=?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->appPath?.?$module;<br><br>????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?加載初始化文件</span><br>????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_file($path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>))?{<br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span>?$path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>;<br>????????}?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">elseif</span>?(is_file(<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->runtimePath?.?$module?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>))?{<br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span>?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->runtimePath?.?$module?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'init.php'</span>;<br>????????}?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">else</span>?{<br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?加載行為擴展文件</span><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_file($path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'tags.php'</span>))?{<br>????????????????$tags?=?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span>?$path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'tags.php'</span>;<br>????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_array($tags))?{<br>????????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->hook->import($tags);<br>????????????????}<br>????????????}<br><br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?加載公共文件</span><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_file($path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'common.php'</span>))?{<br>????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include_once</span>?$path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'common.php'</span>;<br>????????????}<br><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(<span class="hljs-string" style="color: #98c379; line-height: 26px;">''</span>?==?$module)?{<br>????????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?加載系統助手函數</span><br>????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span>?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->thinkPath?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'helper.php'</span>;<br>????????????}<br><br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?加載中間件</span><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_file($path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'middleware.php'</span>))?{<br>????????????????$middleware?=?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span>?$path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'middleware.php'</span>;<br>????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_array($middleware))?{<br>????????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->middleware->import($middleware);<br>????????????????}<br>????????????}<br><br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?注冊服務的容器對象實例</span><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_file($path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'provider.php'</span>))?{<br>????????????????$provider?=?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">include</span>?$path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'provider.php'</span>;<br>????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_array($provider))?{<br>????????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->bindTo($provider);<br>????????????????}<br>????????????}<br><br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>?????????????*?$path?:?"D:phpstudy_proWWWThinkPHPSourceCodeAnalysisapplication"<br>?????????????*??????????"D:phpstudy_proWWWThinkPHPSourceCodeAnalysisapplicationindex"<br>?????????????*/</span><br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?自動讀取配置文件</span><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(is_dir($path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'config'</span>))?{<br>????????????????$dir?=?$path?.?<span class="hljs-string" style="color: #98c379; line-height: 26px;">'config'</span>?.?DIRECTORY_SEPARATOR;<br>????????????}?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">elseif</span>?(is_dir(<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->configPath?.?$module))?{<br>????????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?D:phpstudy_proWWWThinkPHPSourceCodeAnalysisconfig</span><br>????????????????$dir?=?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->configPath?.?$module;<br>????????????}<br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?scandir:以升序的方式讀取目錄中的文件</span><br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?返回就是config目錄中的所有文件</span><br>????????????$files?=?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">isset</span>($dir)???scandir($dir)?:?[];<br><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">foreach</span>?($files?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">as</span>?$file)?{<br>????????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>?????????????????*?$this->configExt:配置文件的后綴<br>?????????????????*?pathinfo返回的是文件后綴,關于pathinfo共有三個可選的參數PATHINFO_DIRNAME、PATHINFO_BASENAME、PATHINFO_EXTENSION,分別為只返回文件名,文件目錄名,文件擴展<br>?????????????????*/</span><br>????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?(<span class="hljs-string" style="color: #98c379; line-height: 26px;">'.'</span>?.?pathinfo($file,?PATHINFO_EXTENSION)?===?<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->configExt)?{<br>????????????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">/**<br>?????????????????????*?倆個參數分別為<br>?????????????????????*?1.目錄+config目錄下的文件<br>?????????????????????*?2.config目錄下文件名<br>?????????????????????*/</span><br>????????????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->config->load($dir?.?$file,?pathinfo($file,?PATHINFO_FILENAME));<br>????????????????}<br>????????????}<br>????????}<br><br>????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->setModulePath($path);<br><br>????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">if</span>?($module)?{<br>????????????<span class="hljs-comment" style="color: #5c6370; font-style: italic; line-height: 26px;">//?對容器中的對象實例進行配置更新</span><br>????????????<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">$this</span>->containerConfigUpdate($module);<br>????????}<br>????}<br>
這里附帶上一份代碼,可以對著代碼看上邊的執行流程,對每一步都做了簡單的說明。
咔咔個人見解對源碼進行優化
在設置模塊的這步代碼咔咔感覺不是很是嚴謹,因為init方法會在倆個地方進行執行。
第一次的模塊為空,這塊代碼執行是沒有任何意義的。
下面在對容器的對象實例進行配置更新時進行了一次判斷,判斷模塊的這個參數是否為空,如果不為空才會執行。
那么同樣的道理,咔咔感覺在設置模塊路徑這塊也應該在這個判斷里邊。
雖說第二次執行會把第一次的結果覆蓋掉,但是咔咔感覺下圖這樣使用才會更好。
四、對容器中的對象實例進行更新配置
在上一節中這里就是最后的內容,那這個對實例進行更新配置,到底更新了什么,怎么更新沒有說明。
在這一小節中就會做出說明,同樣可以配合著前言的思維導圖看。
-
先會把config目錄下的所有配置信息全部獲取出來 -
從app配置文件中將注冊異常處理類 -
第三大塊是把第一步獲取出來的所有配置信息給對應的類進行注冊配置。 -
第四步就是在把模塊確定下來之后加載對應的語言包,語言包功能就可以實現多語言功能,之前咔咔寫過一篇文章實現多語言功能,如果感興趣的可以去查看。 -
最后一步就是根據app配置文件中的三個屬性進行緩存的處理
在這一節中咔咔感覺最重要的就是下圖的內容了。
我們可以隨意追蹤一到倆個方法查看一下那邊到底執行了什么方法。
追蹤方法Db::init()
追蹤方法過來后可以看到就是對Db類中的config屬性進行賦值,把database中的值賦值給Db類中的config屬性。
追蹤方法$this->middleware->setConfig()
來到中間件這個類里邊,可以看到就是把本類的配置和傳遞過來的參數類進行合并,同樣也是進行config屬性的賦值。
跟上邊案例的Db類的init方法實現的效果是一致的。
這里在提一嘴就是在對容器中的對象實例進行更新配置這一幅圖中可以看到紫色部分是在本類中沒有引用的。
那么這是怎么可以進行執行的呢!是因為App類繼承了容器類,容器類中有四個魔術方法,其中有一個__get方法,就是在獲取不存在的屬性時會執行那個方法。
在魔術方法__get方法中執行了一個make方法,這個make方法說了好多次了,這個方法最終會返回一個應用的實例,然后用這個實例調用對應實例類的方法。
這一塊一定要理解好,閱讀源碼就是這個樣子,我們需要對一切未知的進行的解決,只有這樣才能提高我們的編程能力和思想。
五、淺談調試模式以及代碼冗余
本節會對調試模式做出簡單的說明,并且會對框架代碼冗余情況進行簡單的提出。
沒有人寫的代碼是沒有漏洞的,如果有那就是你還沒有達到一定的造詣。
調試模式
在第一節中只提到了initialize方法的上半部分,因為在這一節之前聊的都是關于應用初始化init的內容。
接下來會對這一塊的內容進行簡單的說明。
-
從app配置文件中獲取到app_debug的配置項 -
給環境變量設置debug級別 -
當框架中的debug是關閉狀態時會執行ini_set這個方法,這個方法是為一個配置選項進行賦值。
接下來的內容估計不是很好理解,都是平時在工作中根本使用不到的。
-
ob_get_level:返回輸出緩沖機制的嵌套級別,那么怎么去理解呢!其實就是當緩存區不起作用時會返回0。 -
ob_get_clean:這個函數將會返回輸出緩沖的內容并終止輸出緩沖。如果緩沖區沒有有效內容則返回false。本質上相當于同時執行了ob_getcontens()和ob_end_clean()。 -
ob_start:打開輸出控制緩沖
上邊這三個先暫時認識就行,后期如果有機會會專門出一篇文章做解釋的。
關于框架代碼冗余
這里也僅僅代表咔咔個人的觀點。
可以先看看這部分的代碼,這倆處代碼是不是很是熟悉,沒錯就是在上文的init方法中容器對象實例配置更新見到過。
如圖
這塊也就是咔咔個人提出的見解,由于咔咔式針對5.1做的源碼解讀,不太了解新版版是否做出了改動。
六、總結
本節主要是針對框架執行流程中的初始化應用做了簡單的探討。
至于在app類的run方法下面還有很多的執行過程在這一節中沒有做過多的解釋。
在閱讀源碼的過程中給大家提了一個很好得小技巧,那就是如何去查看一個方法都在哪里進行了執行。
這個方法為debug_backtrace,這個方法需要大家多使用幾次就知道怎么使用了,因為在打印出來的結果中也存在很多無用的信息。
這個方法在調試源碼的過程中是非常有效的,一定要好好利用這個方法。
在就是對初始化應用init方法進行了特別詳細的介紹。
其中咔咔感覺這塊設計最好的就是在容器中的對象實例進行更新配置那一塊,先讀取所有的配置,然后在通過各個類的方法進行配置的設置。
這種代碼規劃和設計思路值得我們去學習。
最后聊到了調試模式和框架的代碼冗余問題,關于調試模式這里咔咔給大家提個醒項目在線上的調試模式一定要關閉。
否則你的項目就類似于裸奔的存在,沒有一點點的安全可言。
這塊有點不好理解的就是對于緩沖區,關于這塊的內容咔咔認為暫時沒有必要去鉆牛角尖,先認識認識然后在進行深入的研究。
緩沖區的這塊內容估計工作了三四年的也很少有人使用,所以先認識,知道怎么一回事,咔咔后期學習了之后在給大家進行補充。
直到這里關于框架的執行流程之初始化應用就結束了,這一節沒有過深需要學習的,主要是其中的代碼設計模式和實現思路。
最后這個圖大家一定要跟著源碼看一看哈!
“
堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。希望在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。
”