如下源碼分析,咱們能夠從 App,Http 類的實例化過程,瞭解類是如何實現自動實例化的,依賴注入是怎麼實現的。
從入口文件出發
當訪問一個 ThinkPHP 搭建的站點,框架最早是從入口文件開始的,而後纔是應用初始化、路由解析、控制器調用和響應輸出等操做。
App 實例化
執行 new App() 實例化時,首先會調用它的構造函數。
構造函數實現了項目各類基礎路徑的初始化,並讀取了 provider.php 文件,將其類的綁定併入 $bind 成員變量,provider.php 文件默認內容以下:
$bind 的值是一組類的標識到類的映射。從這個實現也能夠看出,咱們不只能夠在 provider.php 文件中添加標識到類的映射,並且能夠覆蓋其原有的映射,也就是將某些核心類替換成本身定義的類。
static::setInstance($this) 實現的做用,如圖:
think\App 類的 $instance 成員變量指向 think\App 類的一個實例,也就是類本身保存本身的一個實例。
Http 類的實例化以及依賴注入原理
這裏,$http = (new App())->http,前半部分好理解,後半部分乍一看有點讓人摸不着頭腦,App 類並不存在 http 成員變量,這裏何以大膽調用了一個不存在的東東呢?
原來,App 類繼承自 Container 類,而 Container 類實現了__get() 魔術方法,在 PHP 中,當訪問到的變量不存在,就會觸發__get() 魔術方法。該方法的實現以下:
然而,make()方法主要靠invokeClass()來實現類的實例化。該方法具體分析:
以上代碼可看出,在一個類中,添加__make()方法,在類實例化時,會最早被調用。以上最值得一提的是bindParams()方法:
而這之中,又最值得一提的是getObjectParam()方法:
getObjectParam() 方法再一次光榮地調用 make() 方法,實例化一個類,而這個類,正是從 Http 的構造函數提取的參數,而這個參數又偏偏是一個類的實例 ——App 類的實例。到這裏,程序不只經過 PHP 的反射類實例化了 Http 類,並且實例化了 Http 類的依賴 App 類。假如 App 類又依賴 C 類,C 類又依賴 D類…… 無論多少層,整個依賴鏈條依賴的類均可以實現實例化。
總的來講,整個過程大概是這樣的:須要實例化 Http 類 ==> 提取構造函數發現其依賴 App 類 ==> 開始實例化 App 類(若是發現還有依賴,則一直提取下去,直到天荒地老)==> 將實例化好的依賴(App 類的實例)傳入 Http 類來實例化 Http 類。
這個過程,起個裝逼的名字就叫作「依賴注入」,起個摸不着頭腦的名字,就叫作「控制反轉」。
這個過程,若是退回遠古時代,要實例化 Http 類,大概是這樣實現的(假若有不少層依賴):
這得有多累人。而現代 PHP,交給「容器」就行了。
另外,須要提的一點是 make 方法的 $vars 參數,它的形式能夠是普通數組、關聯數組,並且數組中元素的值能夠是一個類的實例。$vars 參數的值最終將傳遞給要實例化的類的構造函數或者__make 方法中對應的參數。
Request 類的實例化
接上一面,獲得Http類的一個實例後,程序接下來執行$response = $http->run();。其中run()方法代碼以下:
從「request」標識找到要實例化的類
run() 方法的第一行經過容器類實例 app 調用 make() 方法並傳入 Request 類的標識來實例化 Request 類。具體過程以下分析。
經過 make() 方法首先解析獲得 request 標識對應的標識 think\Request, 進一步遞歸解析,又獲得 app\Request 類 —— 這個纔是最終要實例化的類。
app\Request 類對應的文件位於 app 目錄下,代碼以下:
實際上它啥事也沒幹,直接繼承系統的 \think\Request。固然咱們也能夠在這裏對系統的 Request 類進行改寫重構。
調用 invokeClass () 方法
從類的標識解析獲得最終須要實例化的類(單例模式下,且該類還不存在實例)以後,程序調用 invokeClass () 方法,經過 PHP 的反射類實現類的實例化。因爲 \think\Request 類存在__make() 方法,因此實例化以前首先調用該方法。__make() 方法代碼以下:
__make()方法首先實例化think\Request類自身。think\Request類構造函數以下:
構造函數讀取了 php://input 保存起來。接着,__make() 方法保存了一些請求相關的數據,最後返回一個 Request 類實例。最後的最後, make() 方法也成功獲得該實例,整個過程跟 Http 類的實例化相似。該 Request 類實例部分紅員變量如圖:
保存「Request」類的實例到「$instances」數組
獲得 Request 類的實例後,run() 方法接着將該實例保存到「$instance」數組,以便後面單例模式要用到時能夠直接獲取。$instances 數組的值如圖,Request 類的實例已保存在裏面: