解析ThinkPHP6應(yīng)用程序初始化

runWithRequest () 方法

http 類(lèi)的 run() 方法中,得到 thinkrequest 類(lèi)的實(shí)例后,程序接著執(zhí)行 $response = $this->runwithrequest(request); 。其中,runwithrequest() 方法前面幾行如下:

protected function runWithRequest(Request $request) {     $this->initialize();      // 加載全局中間件     $this->loadMiddleware();     .     .     .

該方法第一行執(zhí)行 $this->initialize();,對(duì)應(yīng)用進(jìn)行初始化,接下來(lái)詳細(xì)分析這一初始化操作。 ?
Http 類(lèi)的 initialize() 方法:

protected function initialize() {     //如果還未初始化,則初始化之     if (!$this->app->initialized()) {         $this->app->initialize();     } }

實(shí)際上是調(diào)用 App 類(lèi)的 initialize() 方法。該方法代碼:

public function initialize() {     // 設(shè)置應(yīng)用狀態(tài)為已經(jīng)初始化     $this->initialized = true;      //記錄開(kāi)始時(shí)間     $this->beginTime = microtime(true);     //記錄起始內(nèi)存使用量     $this->beginMem  = memory_get_usage();      // ==( A )== 加載環(huán)境變量     // $this->env跟前面的(new App())->http和$this->config都是同樣的套路     if (is_file($this->rootPath . '.env')) {         $this->env->load($this->rootPath . '.env');     }     //設(shè)置配置文件后綴     $this->configExt = $this->env->get('config_ext', '.php');     // ==( B )== 設(shè)置調(diào)試模式     $this->debugModeInit();      // ==( C )== 加載應(yīng)用文件和配置等操作     $this->load();      // 加載框架默認(rèn)語(yǔ)言包     $langSet = $this->lang->defaultLangSet();     // 框架目錄下對(duì)應(yīng)的語(yǔ)言包     // 比如:D:dev	p6endor	opthinkrameworksrclangzh-cn.php     $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');      // 加載應(yīng)用默認(rèn)語(yǔ)言包     // 這個(gè)會(huì)掃描「app/lang」里面,對(duì)應(yīng)語(yǔ)言包文件夾的所有「.php」文件     // 比如,app/lang/zh-cn/* 下的所有文件     // 然后加載解析     $this->loadLangPack($langSet);      // 監(jiān)聽(tīng)AppInit     $this->Event->trigger('AppInit');      // 設(shè)置時(shí)區(qū)     date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));      // ==( D )== 初始化     // 初始化錯(cuò)誤和異常處理、注冊(cè)系統(tǒng)服務(wù)和初始化系統(tǒng)服務(wù)     foreach ($this->initializers as $initializer) {         $this->make($initializer)->init($this);     }      return $this; }

應(yīng)用的初始化做了大量的操作,其主要的操作有:加載環(huán)境變量、加載配置文件,加載語(yǔ)言包、監(jiān)聽(tīng) AppInit、initializers 數(shù)組包含的類(lèi)的初始化。

(A) 加載環(huán)境變量

對(duì)應(yīng)語(yǔ)句:$this->env->load($this->rootPath . ‘.env’);,其中,$this->env,與前面的 (new App())->http 原理是一樣的(參見(jiàn)第一篇),它可以取得 hinkEnv 類(lèi)的實(shí)例。取得 Env 類(lèi)實(shí)例后,調(diào)用 load() 方法,傳入的參數(shù)是.env 文件所在地址。load() 方法實(shí)現(xiàn)如下:

立即學(xué)習(xí)PHP免費(fèi)學(xué)習(xí)筆記(深入)”;

public function load(string $file): void {     $env = parse_ini_file($file, true) ?: [];     $this->set($env); }

該方法讀取.env 文件的值后,調(diào)用 set() 方法,將配置保存在 Env 類(lèi)的 $data 成員變量。set() 方法代碼:

public function set($env, $value = null): void {     if (is_array($env)) {         //全部KEY轉(zhuǎn)為大寫(xiě)字母         $env = array_change_key_case($env, CASE_UPPER);          foreach ($env as $key => $val) {             //有二級(jí)配置的,轉(zhuǎn)為KEY1_KEY2 => $v 的形式             if (is_array($val)) {                 foreach ($val as $k => $v) {                     $this->data[$key . '_' . strtoupper($k)] = $v;                 }             } else {                 $this->data[$key] = $val;             }         }         //ENV的值不是數(shù)組的情況     } else {         $name = strtoupper(str_replace('.', '_', $env));          $this->data[$name] = $value;     } }

從.env 讀取到的值大概是這樣的: ?
解析ThinkPHP6應(yīng)用程序初始化

$this->set($env) 之后得到的大概是這樣的: ?

解析ThinkPHP6應(yīng)用程序初始化

(B) 調(diào)試模式設(shè)置

$this->debugModeInit() 運(yùn)行原理詳見(jiàn)注釋。

protected function debugModeInit(): void {     // 應(yīng)用調(diào)試模式     if (!$this->appDebug) {         $this->appDebug = $this->env->get('app_debug') ? true : false;         // 關(guān)閉錯(cuò)誤顯示         ini_set('display_errors', 'Off');     }     // 如果不是命令行模式     if (!$this->runningInConsole()) {         // 重新申請(qǐng)一塊比較大的buffer         // php緩沖控制         // 參考:https://www.php.net/manual/en/ref.outcontrol.php         // https://www.cnblogs.com/saw2012/archive/2013/01/30/2882451.html         // 新版PHP默認(rèn)開(kāi)啟緩沖區(qū)ob_start(),ob_get_level() == 1         if (ob_get_level() > 0) {             // 相當(dāng)于ob_get_contents() 和 ob_clean()             // 獲取緩沖區(qū)內(nèi)容并刪除緩沖區(qū)內(nèi)容             $output = ob_get_clean();         }         // 開(kāi)啟新的緩沖區(qū)控制         ob_start();         if (!empty($output)) {             // 由于開(kāi)啟了新的緩沖區(qū)控制,             // 這里不會(huì)直接輸出$output             // 而是等到依次執(zhí)行了ob_flush()和flash()之后才將內(nèi)容輸出到瀏覽器             echo $output;         }     } }

需要注意的是,這里貌似有個(gè) Bug,應(yīng)該先執(zhí)行 $this->appDebug = $this->env->get(‘app_debug’) ? true : false; 獲取是否是調(diào)試模式的配置,然后再判斷:if(!$this->appDebug)。

(C)加載應(yīng)用文件和配置等操作

接下來(lái)執(zhí)行 $this->load();,「load」方法具體實(shí)現(xiàn)如下:

protected function load(): void {     $appPath = $this->getAppPath();      // 加載「app」目錄下的「common.php」文件     if (is_file($appPath . 'common.php')) {         include_once $appPath . 'common.php';     }     // 加載核心目錄下的「helper.php」文件     // 可以看到,這里的加載順序:先「common.php」,后「helper.php」     // 且「helper.php」中的函數(shù)包裹在「if (!function_exists('xxx'))」下     // 所以可以在「common.php」文件中覆蓋系統(tǒng)定義的助手函數(shù)     include_once $this->thinkPath . 'helper.php';      $configPath = $this->getConfigPath();      $files = [];      // glob的作用是掃描給定路徑模式下的文件,非常好用     // 這里掃描「config」目錄下的所有「.php」文件     if (is_dir($configPath)) {         $files = glob($configPath . '*' . $this->configExt);     }      foreach ($files as $file) {         // $this->config 還是同樣的套路獲得了「Config」類(lèi)的實(shí)例         // 「load」的第二個(gè)參數(shù)為一級(jí)配置名,這里傳入一個(gè)文件名,所有文件名作為一級(jí)配置         // 比如「app.php」配置文件,一級(jí)配置為「app」         // 在 「Config」類(lèi)作用域下的操作:         // 「load」加載文件后,通過(guò)「parse」方法解析文件內(nèi)容         // 最后,通過(guò)「set」方法將所有配置合并了「config」成員變量         $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));     }      // 加載「app」目錄下的「event.php」文件     if (is_file($appPath . 'event.php')) {         $this->loadEvent(include $appPath . 'event.php');     }     // 注冊(cè)自定義的服務(wù)     if (is_file($appPath . 'service.php')) {         $services = include $appPath . 'service.php';         foreach ($services as $service) {             $this->register($service);         }     } }

值得一提的是,程序先加載「common.php」,后加載「helper.php」,而「helper.php」中的函數(shù)包裹在「if (!function_exists (‘xxx’))」下,所以我們?nèi)绻行枰梢栽凇竎ommon.php」文件中覆蓋系統(tǒng)定義的助手函數(shù)。

除了加載這兩個(gè)文件,該方法還掃描了「config」目錄下的所有配置文件,并將其加載到 Config 類(lèi)的 $config 成員變量,加載了「app」目錄下的「event.php」文件,以及加載并注冊(cè)自定義的服務(wù)。

(D) 初始化錯(cuò)誤和異常處理、注冊(cè)系統(tǒng)服務(wù)和初始化系統(tǒng)服務(wù)

接著,看初始化函數(shù)的最后一段:

foreach ($this->initializers as $initializer) {     $this->make($initializer)->init($this); }

這幾行代碼做了比較多的操作:分別實(shí)例化包含在里面的類(lèi),然后調(diào)用其「init」方法。initializers 數(shù)組的值如下:

protected $initializers = [     Error::class,  //錯(cuò)誤處理類(lèi)     RegisterService::class, //注冊(cè)系統(tǒng)服務(wù)類(lèi)     BootService::class,  //啟動(dòng)系統(tǒng)服務(wù) ];

略過(guò)系統(tǒng)錯(cuò)誤處理類(lèi),先看注冊(cè)系統(tǒng)服務(wù)類(lèi)。值得注意的是,這個(gè)類(lèi)有一個(gè)成員變量:

protected $services = [     PaginatorService::class,     ValidateService::class,     ModelService::class, ];

包含了三個(gè)系統(tǒng)核心服務(wù)。在其 init 方法中,這些服務(wù)被注冊(cè)到系統(tǒng)服務(wù),與前面的自定義服務(wù)合并起來(lái),其主要實(shí)現(xiàn)代碼:

foreach ($services as $service) {     if (class_exists($service)) {         // 注冊(cè)到系統(tǒng)服務(wù)         $app->register($service);     } }

最后實(shí)例化的是啟動(dòng)系統(tǒng)服務(wù)類(lèi),該類(lèi)的 init 方法僅調(diào)用了 App 類(lèi)的 boot 方法,該方法的作用是初始化每個(gè)系統(tǒng)服務(wù),也就是調(diào)用每個(gè)服務(wù)的 boot 方法。 ?
啟動(dòng)系統(tǒng)服務(wù)類(lèi)實(shí)現(xiàn)如下:

class BootService {     public function init(App $app)     {         $app->boot();     } }

App 類(lèi)的 boot 方法:

public function boot(): void {     array_walk($this->services, function ($service) {         $this->bootService($service);     }); }

其中關(guān)鍵是 bootService 方法:

public function bootService($service) {     if (method_exists($service, 'boot')) {         return $this->invoke([$service, 'boot']);     } }

該方法分別調(diào)用了每個(gè)服務(wù)的 boot 方法,從而初始化已注冊(cè)的服務(wù)。 ?
從以上代碼可以看到,系統(tǒng)注冊(cè)的服務(wù)的來(lái)源有三個(gè)地方:

  1. 系統(tǒng)自帶的,如 PaginatorService,ValidateService,ModelService;
  2. app 目錄下,在「service.php」文件中自定義的;
  3. vendor 目錄下的「service.php」文件定義的。

初始化之后,「App」類(lèi)的實(shí)例大概是這樣子的: ?

解析ThinkPHP6應(yīng)用程序初始化

相關(guān)推薦:最新的10個(gè)thinkphp視頻教程

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊12 分享
站長(zhǎng)的頭像-小浪學(xué)習(xí)網(wǎng)