ThinkPHP6 核心分析之Http 類跟Request類的例項化

it阿布發表於2020-07-23
以下原始碼分析,我們可以從 App,Http 類的例項化過程,瞭解類是如何實現自動例項化的,依賴注入是怎麼實現的。

從入口檔案出發

當訪問一個 ThinkPHP 搭建的站點,框架最先是從入口檔案開始的,然後才是應用初始化、路由解析、控制器呼叫和響應輸出等操作。
 
入口檔案主要程式碼如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化

App 例項化

執行 new App() 例項化時,首先會呼叫它的建構函式。
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
建構函式實現了專案各種基礎路徑的初始化,並讀取了 provider.php 檔案,將其類的繫結併入 $bind 成員變數,provider.php 檔案預設內容如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
合併後,$bind 成員變數的值如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
$bind 的值是一組類的標識到類的對映。從這個實現也可以看出,我們不僅可以在 provider.php 檔案中新增標識到類的對映,而且可以覆蓋其原有的對映,也就是將某些核心類替換成自己定義的類。
 
static::setInstance($this) 實現的作用,如圖:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
think\App 類的 $instance 成員變數指向 think\App 類的一個例項,也就是類自己儲存自己的一個例項。
instance() 方法的實現:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
其中的getAlias方法:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
執行結果大概是這樣的:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化

Http 類的例項化以及依賴注入原理

這裡,$http = (new App())->http,前半部分好理解,後半部分乍一看有點讓人摸不著頭腦,App 類並不存在 http 成員變數,這裡何以大膽呼叫了一個不存在的東東呢?
 
原來,App 類繼承自 Container 類,而 Container 類實現了__get() 魔術方法,在 PHP 中,當訪問到的變數不存在,就會觸發__get() 魔術方法。該方法的實現如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
實際上是呼叫get()方法:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
然而,實際上,主要是make()方法:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
然而,make()方法主要靠invokeClass()來實現類的例項化。該方法具體分析:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
以上程式碼可看出,在一個類中,新增__make()方法,在類例項化時,會最先被呼叫。以上最值得一提的是bindParams()方法:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
而這之中,又最值得一提的是getObjectParam()方法:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
getObjectParam() 方法再一次光榮地呼叫 make() 方法,例項化一個類,而這個類,正是從 Http 的建構函式提取的引數,而這個引數又恰恰是一個類的例項 ——App 類的例項。到這裡,程式不僅通過 PHP 的反射類例項化了 Http 類,而且例項化了 Http 類的依賴 App 類。假如 App 類又依賴 C 類,C 類又依賴 D類…… 不管多少層,整個依賴鏈條依賴的類都可以實現例項化。
 
總的來說,整個過程大概是這樣的:需要例項化 Http 類 ==> 提取建構函式發現其依賴 App 類 ==> 開始例項化 App 類(如果發現還有依賴,則一直提取下去,直到天荒地老)==> 將例項化好的依賴(App 類的例項)傳入 Http 類來例項化 Http 類。
 
這個過程,起個裝逼的名字就叫做「依賴注入」,起個摸不著頭腦的名字,就叫做「控制反轉」。
 
這個過程,如果退回遠古時代,要例項化 Http 類,大概是這樣實現的(假如有很多層依賴):
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
這得有多累人。而現代 PHP,交給「容器」就好了。
 
另外,需要提的一點是 make 方法的 $vars 引數,它的形式可以是普通陣列、關聯陣列,而且陣列中元素的值可以是一個類的例項。$vars 引數的值最終將傳遞給要例項化的類的建構函式或者__make 方法中對應的引數。
 

Request 類的例項化

接上一面,得到Http類的一個例項後,程式接下來執行$response = $http->run();。其中run()方法程式碼如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 

從「request」標識找到要例項化的類

run() 方法的第一行通過容器類例項 app 呼叫 make() 方法並傳入 Request 類的標識來例項化 Request 類。具體過程如下分析。
 
通過 make() 方法首先解析得到 request 標識對應的標識 think\Request, 進一步遞迴解析,又得到 app\Request 類 —— 這個才是最終要例項化的類。
 
app\Request 類對應的檔案位於 app 目錄下,程式碼如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
實際上它啥事也沒幹,直接繼承系統的 \think\Request。當然我們也可以在這裡對系統的 Request 類進行改寫重構。

呼叫 invokeClass () 方法

從類的標識解析得到最終需要例項化的類(單例模式下,且該類還不存在例項)之後,程式呼叫 invokeClass () 方法,通過 PHP 的反射類實現類的例項化。由於 \think\Request 類存在__make() 方法,所以例項化之前首先呼叫該方法。__make() 方法程式碼如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
__make()方法首先例項化think\Request類自身。think\Request類建構函式如下:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
建構函式讀取了 php://input 儲存起來。接著,__make() 方法儲存了一些請求相關的資料,最後返回一個 Request 類例項。最後的最後, make() 方法也成功得到該例項,整個過程跟 Http 類的例項化類似。該 Request 類例項部分成員變數如圖:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化

儲存「Request」類的例項到「$instances」陣列

得到 Request 類的例項後,run() 方法接著將該例項儲存到「$instance」陣列,以便後面單例模式要用到時可以直接獲取。$instances 陣列的值如圖,Request 類的例項已儲存在裡面:
 
ThinkPHP6 核心分析之Http 類跟Request類的例項化
 
 

相關文章