天天看點

ThinkPHP架構執行流程源碼解析

本文主要介紹架構的執行流程

ThinkPHP架構執行流程

  • ​​前言​​
  • ​​一、架構執行流程之初始化應用的資料設定​​
  • ​​二、如何檢視一個方法都在哪裡執行了​​
  • ​​三、架構執行流程之初始化應用init分析​​
  • ​​四、對容器中的對象執行個體進行更新配置​​
  • ​​五、淺談調試模式以及代碼備援​​
  • ​​六、總結​​

前言

如果不清楚架構是怎麼執行的,那麼看在多的代碼都是隻是認識代碼而已,閱讀源碼是為了學習其架構的設計思想和代碼模式。

而執行流程則是将我們學習的東西串聯在一起,進而更好地了解。咔咔也會給大家把執行流程用思維導圖的方式畫出來。

隻要大家在本文學習到一點點的知識點,咔咔也是心滿意足的。

這個流程圖隻是針對initialize的執行過程,其餘的執行過程後期會進行補充,都是以腦圖的形式呈現給大家的。

ThinkPHP架構執行流程源碼解析

一、架構執行流程之初始化應用的資料設定

這裡的内容跟容器的内容有點重複,因為執行流程是從入口檔案開始的,并且最後也是通過容器執行的。

ThinkPHP架構執行流程源碼解析

然後就會進入到檔案​

​thinkphp/library/think/App.php​

​的run方法,在這個方法中主要就是下圖框出來的地方,執行的initialize方法。

ThinkPHP架構執行流程源碼解析

來到initialize這個方法,先看上半部分。

  • ​microtime(true);​

    ​傳回的是unix的微秒數
  • ​memory_get_usage​

    ​傳回的是配置設定給PHP的記憶體量,機關為位元組
  • 在接下來就是對架構的幾個路徑進行設定
  • ​static::setInstance($this);​

    ​這裡是将app這個執行個體設定為容器執行個體
  • ​$this->instance('app', $this);​

    ​這個在之前容器章節就提到了,就是為了把app這個類綁定到容器裡邊去,也就是注冊樹模式。
ThinkPHP架構執行流程源碼解析

這裡有一個小的問題點給大家提出來,在初始化應用的這個方法裡邊存在這樣一行代碼。

有沒有小夥伴對這個​

​$this->env​

​和下邊的​

​$this->config​

​這倆個調用有疑惑。

如果你有疑惑那就跟着咔咔一起來看,沒疑惑的就可以繼續往下看了。

App這個類是繼承的容器類,那麼這個env和config不論是在app還是container類中都是沒有這倆個屬性的。

那麼怎麼就可以直接調用呢!而且代碼追蹤都會追蹤到env類和container類中。

需要知道這個源頭就需要我們去在大緻的看一遍container類的代碼。

ThinkPHP架構執行流程源碼解析

經過一番苦讀之後,可以看到下圖的幾行代碼。這幾行代碼全部使用的是魔術方法。

當通路env類不存在的時候就會去執行make方法。

make這個方法在容器那一章節進行的細的不能再細的解讀了。

這個make方法最終會傳回一個類的執行個體,并且還會存到容器裡邊。

ThinkPHP架構執行流程源碼解析

這裡隻放一個make方法的代碼,如果有不會的可以去看之前的文章。

ThinkPHP架構執行流程源碼解析

最後就是加載一系列的資料,加載詳情請看前言的思維導圖。

ThinkPHP架構執行流程源碼解析

二、如何檢視一個方法都在哪裡執行了

在閱讀源碼的過程中,有一個很難把控的問題就是一個方法在不同的地方進行了調用,但是咱們确一時半會根本不知道都在哪裡調用了。

這裡用init方法來做一個示範。

init方法是初始化應用或者子產品的一個方法,但是這裡的module參數确實一個空值。

ThinkPHP架構執行流程源碼解析

先做一個斷點檢視一下相關的資料資訊。

列印的結果就是空,這就是一些新學習的夥伴會犯的一個錯誤,因為這個方法不可能隻調用一次的。

如果初始化子產品都是空那麼這個方法就沒有存在的必要了。

ThinkPHP架構執行流程源碼解析
ThinkPHP架構執行流程源碼解析

那麼正确的斷點方式應該是這個樣子的。

ThinkPHP架構執行流程源碼解析
ThinkPHP架構執行流程源碼解析

此時就會有一個問題,這個init方法明顯是被調用了倆次的,那麼另一次調用的地方是在哪裡呢!

如果在不知道新的技巧之前,就會進行一系列的斷點列印,看在哪裡進行了執行,比如在這個init的上層去列印。

也就是在initialize那個方法裡邊去列印做斷點,但是這樣很是麻煩的,而且很有可能浪費了大量的時間還是找不到正确的地方。

小技巧之debug_backtrace()

這個方法會産生一條回溯追蹤,會顯示出一個方法所有的調用位置。

使用方式就是如下圖,隻需要把debug_backtrace這個方法列印出來即可。

ThinkPHP架構執行流程源碼解析
ThinkPHP架構執行流程源碼解析
ThinkPHP架構執行流程源碼解析

根據得到的資料資訊,就可以非常快的進行定位。

第一次就是在app類的215行。

ThinkPHP架構執行流程源碼解析

第二次是在​

​thinkphp/library/think/route/dispatch/Module.php​

​類的60行

ThinkPHP架構執行流程源碼解析

可以在這裡做一個列印,看一下這個module是否為index

ThinkPHP架構執行流程源碼解析
ThinkPHP架構執行流程源碼解析

是以說有了這個方法就可以非常快速地定位調用位置。

三、架構執行流程之初始化應用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方法會在倆個地方進行執行。

第一次的子產品為空,這塊代碼執行是沒有任何意義的。

下面在對容器的對象執行個體進行配置更新時進行了一次判斷,判斷子產品的這個參數是否為空,如果不為空才會執行。

那麼同樣的道理,咔咔感覺在設定子產品路徑這塊也應該在這個判斷裡邊。

雖說第二次執行會把第一次的結果覆寫掉,但是咔咔感覺下圖這樣使用才會更好。

ThinkPHP架構執行流程源碼解析

四、對容器中的對象執行個體進行更新配置

在上一節中這裡就是最後的内容,那這個對執行個體進行更新配置,到底更新了什麼,怎麼更新沒有說明。

在這一小節中就會做出說明,同樣可以配合着前言的思維導圖看。

  • 先會把config目錄下的所有配置資訊全部擷取出來
  • 從app配置檔案中将注冊異常處理類
  • 第三大塊是把第一步擷取出來的所有配置資訊給對應的類進行注冊配置。
  • 第四步就是在把子產品确定下來之後加載對應的語言包,語言包功能就可以實作多語言功能,之前咔咔寫過一篇文章實作多語言功能,如果感興趣的可以去檢視。
  • 最後一步就是根據app配置檔案中的三個屬性進行緩存的處理

在這一節中咔咔感覺最重要的就是下圖的内容了。

ThinkPHP架構執行流程源碼解析

我們可以随意追蹤一到倆個方法檢視一下那邊到底執行了什麼方法。

追蹤方法Db::init()

追蹤方法過來後可以看到就是對Db類中的config屬性進行指派,把database中的值指派給Db類中的config屬性。

ThinkPHP架構執行流程源碼解析

追蹤方法$this->middleware->setConfig()

來到中間件這個類裡邊,可以看到就是把本類的配置和傳遞過來的參數類進行合并,同樣也是進行config屬性的指派。

跟上邊案例的Db類的init方法實作的效果是一緻的。

這裡在提一嘴就是在​

​對容器中的對象執行個體進行更新配置​

​這一幅圖中可以看到紫色部分是在本類中沒有引用的。

那麼這是怎麼可以進行執行的呢!是因為App類繼承了容器類,容器類中有四個魔術方法,其中有一個__get方法,就是在擷取不存在的屬性時會執行那個方法。

在魔術方法__get方法中執行了一個make方法,這個make方法說了好多次了,這個方法最終會傳回一個應用的執行個體,然後用這個執行個體調用對應執行個體類的方法。

這一塊一定要了解好,閱讀源碼就是這個樣子,我們需要對一切未知的進行的解決,隻有這樣才能提高我們的程式設計能力和思想。

ThinkPHP架構執行流程源碼解析

五、淺談調試模式以及代碼備援

本節會對調試模式做出簡單的說明,并且會對架構代碼備援情況進行簡單的提出。

沒有人寫的代碼是沒有漏洞的,如果有那就是你還沒有達到一定的造詣。

調試模式

在第一節中隻提到了initialize方法的上半部分,因為在這一節之前聊的都是關于應用初始化init的内容。

接下來會對這一塊的内容進行簡單的說明。

  • 從app配置檔案中擷取到app_debug的配置項
  • 給環境變量設定debug級别
  • 當架構中的debug是關閉狀态時會執行ini_set這個方法,這個方法是為一個配置選項進行指派。

接下來的内容估計不是很好了解,都是平時在工作中根本使用不到的。

  • ob_get_level:傳回輸出緩沖機制的嵌套級别,那麼怎麼去了解呢!其實就是當緩存區不起作用時會傳回0。
  • ob_get_clean:這個函數将會傳回輸出緩沖的内容并終止輸出緩沖。如果緩沖區沒有有效内容則傳回false。本質上相當于同時執行了ob_getcontens()和ob_end_clean()。
  • ob_start:打開輸出控制緩沖

上邊這三個先暫時認識就行,後期如果有機會會專門出一篇文章做解釋的。

ThinkPHP架構執行流程源碼解析

關于架構代碼備援

這裡也僅僅代表咔咔個人的觀點。

可以先看看這部分的代碼,這倆處代碼是不是很是熟悉,沒錯就是在上文的init方法中容器對象執行個體配置更新見到過。

ThinkPHP架構執行流程源碼解析

如圖

ThinkPHP架構執行流程源碼解析

這塊也就是咔咔個人提出的見解,由于咔咔式針對5.1做的源碼解讀,不太了解新版版是否做出了改動。

六、總結

本節主要是針對架構執行流程中的初始化應用做了簡單的探讨。

至于在app類的run方法下面還有很多的執行過程在這一節中沒有做過多的解釋。

在閱讀源碼的過程中給大家提了一個很好得小技巧,那就是如何去檢視一個方法都在哪裡進行了執行。

這個方法為​

​debug_backtrace​

​,這個方法需要大家多使用幾次就知道怎麼使用了,因為在列印出來的結果中也存在很多無用的資訊。

這個方法在調試源碼的過程中是非常有效的,一定要好好利用這個方法。

在就是對初始化應用init方法進行了特别詳細的介紹。

其中咔咔感覺這塊設計最好的就是在容器中的對象執行個體進行更新配置那一塊,先讀取所有的配置,然後在通過各個類的方法進行配置的設定。

這種代碼規劃和設計思路值得我們去學習。

最後聊到了調試模式和架構的代碼備援問題,關于調試模式這裡咔咔給大家提個醒項目線上上的調試模式一定要關閉。

否則你的項目就類似于裸奔的存在,沒有一點點的安全可言。

這塊有點不好了解的就是對于緩沖區,關于這塊的内容咔咔認為暫時沒有必要去鑽牛角尖,先認識認識然後在進行深入的研究。

緩沖區的這塊内容估計工作了三四年的也很少有人使用,是以先認識,知道怎麼一回事,咔咔後期學習了之後在給大家進行補充。

直到這裡關于架構的執行流程之初始化應用就結束了,這一節沒有過深需要學習的,主要是其中的代碼設計模式和實作思路。

最後這個圖大家一定要跟着源碼看一看哈!

ThinkPHP架構執行流程源碼解析