本文主要介紹框架的執行流程php
前言
若是不清楚框架是怎麼執行的,那麼看在多的代碼都是隻是認識代碼而已,閱讀源碼是爲了學習其框架的設計思想和代碼模式。thinkphp
而執行流程則是將咱們學習的東西串聯在一塊兒,從而更好地理解。咔咔也會給你們把執行流程用思惟導圖的方式畫出來。編程
只要你們在本文學習到一點點的知識點,咔咔也是心滿意足的。設計模式
這個流程圖只是針對initialize的執行過程,其他的執行過程後期會進行補充,都是以腦圖的形式呈現給你們的。緩存
1、框架執行流程之初始化應用的數據設置
這裏的內容跟容器的內容有點重複,由於執行流程是從入口文件開始的,而且最後也是經過容器執行的。安全
而後就會進入到文件thinkphp/library/think/App.php
的run方法,在這個方法中主要就是下圖框出來的地方,執行的initialize方法。app
來到initialize這個方法,先看上半部分。框架
microtime(true);
返回的是unix的微秒數memory_get_usage
返回的是分配給PHP的內存量,單位爲字節- 在接下來就是對框架的幾個路徑進行設置
static::setInstance($this);
這裏是將app這個實例設置爲容器實例$this->instance('app', $this);
這個在以前容器章節就提到了,就是爲了把app這個類綁定到容器裏邊去,也就是註冊樹模式。
這裏有一個小的問題點給你們提出來,在初始化應用的這個方法裏邊存在這樣一行代碼。ide
有沒有小夥伴對這個$this->env
和下邊的$this->config
這倆個調用有疑惑。函數
若是你有疑惑那就跟着咔咔一塊兒來看,沒疑惑的就能夠繼續往下看了。
App這個類是繼承的容器類,那麼這個env和config不管是在app仍是container類中都是沒有這倆個屬性的。
那麼怎麼就能夠直接調用呢!並且代碼追蹤都會追蹤到env類和container類中。
須要知道這個源頭就須要咱們去在大體的看一遍container類的代碼。
通過一番苦讀以後,能夠看到下圖的幾行代碼。這幾行代碼所有使用的是魔術方法。
當訪問env類不存在的時候就會去執行make方法。
make這個方法在容器那一章節進行的細的不能再細的解讀了。
這個make方法最終會返回一個類的實例,而且還會存到容器裏邊。
這裏只放一個make方法的代碼,若是有不會的能夠去看以前的文章。
最後就是加載一系列的數據,加載詳情請看前言的思惟導圖。
2、如何查看一個方法都在哪裏執行了
在閱讀源碼的過程當中,有一個很難把控的問題就是一個方法在不一樣的地方進行了調用,可是我們確一時半會根本不知道都在哪裏調用了。
這裏用init方法來作一個演示。
init方法是初始化應用或者模塊的一個方法,可是這裏的module參數確實一個空值。
先作一個斷點查看一下相關的數據信息。
打印的結果就是空,這就是一些新學習的夥伴會犯的一個錯誤,由於這個方法不可能只調用一次的。
若是初始化模塊都是空那麼這個方法就沒有存在的必要了。
那麼正確的斷點方式應該是這個樣子的。
此時就會有一個問題,這個init方法明顯是被調用了倆次的,那麼另外一次調用的地方是在哪裏呢!
若是在不知道新的技巧以前,就會進行一系列的斷點打印,看在哪裏進行了執行,好比在這個init的上層去打印。
也就是在initialize那個方法裏邊去打印作斷點,可是這樣非常麻煩的,並且頗有可能浪費了大量的時間仍是找不到正確的地方。
小技巧之debug_backtrace()
這個方法會產生一條回溯追蹤,會顯示出一個方法全部的調用位置。
使用方式就是以下圖,只須要把debug_backtrace這個方法打印出來便可。
根據獲得的數據信息,就能夠很是快的進行定位。
第一次就是在app類的215行。
第二次是在thinkphp/library/think/route/dispatch/Module.php
類的60行
能夠在這裏作一個打印,看一下這個module是否爲index
因此說有了這個方法就能夠很是快速地定位調用位置。
3、框架執行流程之初始化應用init分析
上文給你們提供了一個小技巧debug_backtrace
實戰演示瞭如何查看一個方法都在哪裏執行的。
而且案例也是使用的init這個方法來演示的,由於接下來就是要對init這個方法進行深刻的瞭解。
在init方法裏邊主要作的事情在上邊的腦圖已經描述的很清楚了。
- 從一開始就對模塊的定位,就是在第二節中的對init方法的調用,會傳入對應的模塊
- 加載app目錄下的tags文件,在tags文件裏邊就是對行爲擴展定義的文件。在以前門面的文章中定義鉤子執行就在這個文件中設置的。
- 加載common文件,也就是公共文件,因此說公共文件就是在這裏進行加載的。
- 加載助手函數文件helper,在助手函數裏邊有一個你們特別熟悉的一個方法,那就是dump。這就是爲何在有的地方使用dump會報錯的緣由。
- 加載中間件文件,這裏的直接給出的是直接加載app目錄下的中間件文件,可是在框架中咱們須要在定義一個目錄爲http,在這個目錄下定義中間件文件。
- 註冊服務的容器對象實例,這裏註冊就使用的是容器類中的
bindTo
方法進行綁定註冊的。 - 讀取配置文件,這段在配置文件加載那一節中已經進行深刻的說明了, 這裏就不提了。配置文件會讀取倆個地方一個是第一步模塊下的config文件,另外一個就是config目錄下的配置文件。
- 設置模塊路徑,會把第一步獲取到的模塊進行
env
環境變量配置裏邊 - 最後一步就是對容器中的對象實例進行配置更新,具體更新了什麼在後文中給你們詳細說來。
/** * 初始化應用或模塊 * @access public * @param string $module 模塊名 * @return void */ public function init($module = '') { // 定位模塊目錄 $module = $module ? $module . DIRECTORY_SEPARATOR : ''; /** * 第一次:D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\ * 第二次:D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\index\ */ $path = $this->appPath . $module; // 加載初始化文件 if (is_file($path . 'init.php')) { include $path . 'init.php'; } elseif (is_file($this->runtimePath . $module . 'init.php')) { include $this->runtimePath . $module . 'init.php'; } else { // 加載行爲擴展文件 if (is_file($path . 'tags.php')) { $tags = include $path . 'tags.php'; if (is_array($tags)) { $this->hook->import($tags); } } // 加載公共文件 if (is_file($path . 'common.php')) { include_once $path . 'common.php'; } if ('' == $module) { // 加載系統助手函數 include $this->thinkPath . 'helper.php'; } // 加載中間件 if (is_file($path . 'middleware.php')) { $middleware = include $path . 'middleware.php'; if (is_array($middleware)) { $this->middleware->import($middleware); } } // 註冊服務的容器對象實例 if (is_file($path . 'provider.php')) { $provider = include $path . 'provider.php'; if (is_array($provider)) { $this->bindTo($provider); } } /** * $path : "D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\" * "D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\application\index\" */ // 自動讀取配置文件 if (is_dir($path . 'config')) { $dir = $path . 'config' . DIRECTORY_SEPARATOR; } elseif (is_dir($this->configPath . $module)) { // D:\phpstudy_pro\WWW\ThinkPHPSourceCodeAnalysis\config\ $dir = $this->configPath . $module; } // scandir:以升序的方式讀取目錄中的文件 // 返回就是config目錄中的全部文件 $files = isset($dir) ? scandir($dir) : []; foreach ($files as $file) { /** * $this->configExt:配置文件的後綴 * pathinfo返回的是文件後綴,關於pathinfo共有三個可選的參數PATHINFO_DIRNAME、PATHINFO_BASENAME、PATHINFO_EXTENSION,分別爲只返回文件名,文件目錄名,文件擴展 */ if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { /** * 倆個參數分別爲 * 1.目錄+config目錄下的文件 * 2.config目錄下文件名 */ $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); } } } $this->setModulePath($path); if ($module) { // 對容器中的對象實例進行配置更新 $this->containerConfigUpdate($module); } }
這裏附帶上一份代碼,能夠對着代碼看上邊的執行流程,對每一步都作了簡單的說明。
咔咔我的看法對源碼進行優化
在設置模塊的這步代碼咔咔感受不是非常嚴謹,由於init方法會在倆個地方進行執行。
第一次的模塊爲空,這塊代碼執行是沒有任何意義的。
下面在對容器的對象實例進行配置更新時進行了一次判斷,判斷模塊的這個參數是否爲空,若是不爲空纔會執行。
那麼一樣的道理,咔咔感受在設置模塊路徑這塊也應該在這個判斷裏邊。
雖然說第二次執行會把第一次的結果覆蓋掉,可是咔咔感受下圖這樣使用纔會更好。
4、對容器中的對象實例進行更新配置
在上一節中這裏就是最後的內容,那這個對實例進行更新配置,到底更新了什麼,怎麼更新沒有說明。
在這一小節中就會作出說明,一樣能夠配合着前言的思惟導圖看。
- 先會把config目錄下的全部配置信息所有獲取出來
- 從app配置文件中將註冊異常處理類
- 第三大塊是把第一步獲取出來的全部配置信息給對應的類進行註冊配置。
- 第四步就是在把模塊肯定下來以後加載對應的語言包,語言包功能就能夠實現多語言功能,以前咔咔寫過一篇文章實現多語言功能,若是感興趣的能夠去查看。
- 最後一步就是根據app配置文件中的三個屬性進行緩存的處理
在這一節中咔咔感受最重要的就是下圖的內容了。
咱們能夠隨意追蹤一到倆個方法查看一下那邊到底執行了什麼方法。
追蹤方法Db::init()
追蹤方法過來後能夠看到就是對Db類中的config屬性進行賦值,把database中的值賦值給Db類中的config屬性。
追蹤方法$this->middleware->setConfig()
來到中間件這個類裏邊,能夠看到就是把本類的配置和傳遞過來的參數類進行合併,一樣也是進行config屬性的賦值。
跟上邊案例的Db類的init方法實現的效果是一致的。
這裏在提一嘴就是在對容器中的對象實例進行更新配置
這一幅圖中能夠看到紫色部分是在本類中沒有引用的。
那麼這是怎麼能夠進行執行的呢!是由於App類繼承了容器類,容器類中有四個魔術方法,其中有一個__get方法,就是在獲取不存在的屬性時會執行那個方法。
在魔術方法__get方法中執行了一個make方法,這個make方法說了好屢次了,這個方法最終會返回一個應用的實例,而後用這個實例調用對應實例類的方法。
這一塊必定要理解好,閱讀源碼就是這個樣子,咱們須要對一切未知的進行的解決,只有這樣才能提升咱們的編程能力和思想。
5、淺談調試模式以及代碼冗餘
本節會對調試模式作出簡單的說明,而且會對框架代碼冗餘狀況進行簡單的提出。
沒有人寫的代碼是沒有漏洞的,若是有那就是你尚未達到必定的造詣。
調試模式
在第一節中只提到了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作的源碼解讀,不太瞭解新版版是否作出了改動。
6、總結
本節主要是針對框架執行流程中的初始化應用作了簡單的探討。
至於在app類的run方法下面還有不少的執行過程在這一節中沒有作過多的解釋。
在閱讀源碼的過程當中給你們提了一個很好得小技巧,那就是如何去查看一個方法都在哪裏進行了執行。
這個方法爲debug_backtrace
,這個方法須要你們多使用幾回就知道怎麼使用了,由於在打印出來的結果中也存在不少無用的信息。
這個方法在調試源碼的過程當中是很是有效的,必定要好好利用這個方法。
在就是對初始化應用init方法進行了特別詳細的介紹。
其中咔咔感受這塊設計最好的就是在容器中的對象實例進行更新配置那一塊,先讀取全部的配置,而後在經過各個類的方法進行配置的設置。
這種代碼規劃和設計思路值得咱們去學習。
最後聊到了調試模式和框架的代碼冗餘問題,關於調試模式這裏咔咔給你們提個醒項目在線上的調試模式必定要關閉。
不然你的項目就相似於裸奔的存在,沒有一點點的安全可言。
這塊有點很差理解的就是對於緩衝區,關於這塊的內容咔咔認爲暫時沒有必要去鑽牛角尖,先認識認識而後在進行深刻的研究。
緩衝區的這塊內容估計工做了三四年的也不多有人使用,因此先認識,知道怎麼一回事,咔咔後期學習了以後在給你們進行補充。
直到這裏關於框架的執行流程之初始化應用就結束了,這一節沒有過深須要學習的,主要是其中的代碼設計模式和實現思路。
最後這個圖你們必定要跟着源碼看一看哈!
堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。但願在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。