### :-: **介紹**
>[danger]
> **在閱讀此章節之前先閱讀完 “耦合與解耦”和“反射類”章節**
> **如果要自行追蹤代碼學習,必須確保斷點調試環境已經搭建好,參考“斷點調試技巧章節”**
“耦合與解耦”章節我們介紹到在軟件的設計過程中解耦的意義和基本原理,在軟件涉及的功能過多的時候,僅僅依賴該章節里講到的方法是遠遠達不到要求的,不夠智能,不夠自動化,而控制反轉的思想就是為了更方便更自動化的解決類之間耦合的問題,簡單的說就是在一個類的方法里需要另一個類的實例時,不需要你自己去 new 了。
>[danger]
> **很多朋友可能沒搞清楚控制反轉和依賴注入的區別,誤以為控制反轉就是依賴注入,事實并非如此,控制反轉是一種機制,通俗的說就是有一個專門的類來管理我們需要 new 的對象,而依賴注入是實現控制反轉的一種方式而已,除了依賴注入,還有依賴查找等其他方式**
### :-: **thinkphp5.0 里的依賴注入實現**
這里我們用thinkphp5官方介紹的依賴注入做實例,講解他的實現機制
手冊參考 http://www.hmoore.net/manual/thinkphp5/215849
**這是控制器里的一個方法,我們使用傳統的 new 方式得到一個類的實例**
> ```
> public function testInjection()
> {
> $Spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
>
> var_dump($Spreadsheet);exit;
> }
>
> ```
> 
**這里我們用tp的依賴注入機制實現**
> ```
> public function testInjection(\PhpOffice\PhpSpreadsheet\Spreadsheet $Spreadsheet)
> {
> var_dump($Spreadsheet);exit;
> }
> ```
> 
**同樣可以得到這么一個實例,那么這個機制是怎么實現的?**
**這里需要大家有反射類的基礎,不懂的請移步“反射類”章節
需要理解這個問題我們要先搞清楚這個控制器類是怎么被實例化的,這個 testInjection 方法是如何被調用的**
> 在瀏覽器里請求這個地址 `http://local2.cc/demo/example/testInjection `
> 
> 代碼執行到 `App.php:139, think\App::run()` 這個方法,我們在 `self::exec($dispatch, $config)` 這個位置打一個斷點,打開` $dispatch `這個參數的 module屬性,會發現系統已經解析了我們請求的 url `http://local2.cc/demo/example/testInjection `后面的 `demo/example/testInjection` 這個就是后面定位到的模塊,控制器,方法
> 
> 我們跟進 `self::exec($dispatch, $config)` 方法,571行的 `Loader::controller` 方法調用。控制器本身就是一個類,要調用里面的方法必須先實例化,這行的作用就是實例化我們的控制器,繼續跟進
> 
跟進后發現這里調用的 `App::invokeClass` 方法,再次跟進
> 
> 355 行就是建立控制器 `app\demo\controller\Example` 的反射類,356行 獲取他的構造方法,357 行是判斷這個控制器有沒有定義構造方法 `__construct`,如果有定義,就調用 `self::bindParams` 方法計算`__construct`應該傳入的對象,`self::bindParams` 這個是方法的實現我們在下面調用 `testInjection` 方法的時候講解,在 359 行就是調用 newInstanceArgs 方法,返回一個控制器的實例,效果和 `new app\demo\controller\Example()` 一樣。之所有要使用反射來獲取而不用 new 就是為了 356 行拿到那個構造方法,實現自動注入。
> 
> 執行完 571行的 `Loader::controller` 方法以后 `$instance` 已經為控制器的實例,在 589 行又建立了 `testInjection` 方法的反射,`new \ReflectionMethod($instance, $action)` 中,`$instance` 為控制器對象實例,`$action` 則為方法名,這一步實際為輔助方法,跟核心邏輯沒太大關系,在 `config.php` 配置里,有個 `'action_suffix' `配置,這里實際就是添加這個方法前綴。最終得到的方法名為 `$actionName` 這個變量。
> 
> 控制器的實例有了( `$instance` ),方法名有了( `$actionName`),下一步就可以開始執行方法了,在606行就是執行這個方法,我們跟進` self::invokeMethod($call, $vars)` 方法
> 
> 333 行建立了 `testinjection` 方法的反射,339 行就是依賴注入最核心的實現了,我們跟進
> 
> 這個方法傳入的第一個參數` $reflect` 為` testInjection` 方法的反射,第二個參數` $vars `是我們在請求的時候url后面傳遞的參數,這里我們不管,在 379 行執行了一個判斷 `$reflect->getNumberOfParameters()` ,這行的意思是拿到` testInjection `這個方法需要傳遞幾個參數,參考我們之前定義的 `testInjection `方法,我們一共傳遞了一個參數 `$Spreadsheet`,并且強制指定這個參數是個 `\PhpOffice\PhpSpreadsheet\Spreadsheet` 對象,而這個強制指定,就是后面依賴注入的依據。
> 
> 代碼走到 384 行,這里 `$reflect->getParameters()` 這個方法的實質是遍歷傳入的參數,上圖圈起來的部分,`name `等于 `Spreadsheet `,這個 `Spreadsheet `就是定義 `testInjection `這個方法參數中的 `Spreadsheet`,而 `$param` 這個變量實質是個 `ReflectionParameter `對象,也就是 `testInjection` 方法里的參數`$Spreadsheet`的反射。
> 
> 385 行調用了 ` self::getParamValue` 這個方法,這個就是根據強制指定對象類型實例化這個對象的方法了,我們跟進
> 
> 403 行,調用 `$param->getClass()` 方法,可以拿到`$Spreadsheet`參數強制指定的類型是`\PhpOffice\PhpSpreadsheet\Spreadsheet`,中間經過一系列的判斷,我們不用管,一般用不到。在 422 行,這里有個一 `new $className `的動作,`\PhpOffice\PhpSpreadsheet\Spreadsheet`這個對象在這里被實例化,隨后返回,返回的結果加到 `$args `這個變量上。
> 
> 返回到 ` self::invokeMethod($call, $vars)` 方法,下面就是調用 `$reflect->invokeArgs` 執行我們定義的 `testInjection `方法,如果你明白 `invokeArgs `這個方法的用法,那么就一定知道這個參數是如何被傳進去的。
> 
> 再次跟進的時候,會發現代碼已經執行到了我們自己定義的方法里,至此,注入機制完成
> 
>[danger]
> **這里有朋友可能會發現,似乎整個過程都沒有提及到控制反轉和容器,事實上根據我對這個過程的觀察,在tp5.0里確實沒用到所謂的容器,但是這個跟依賴注入并不沖突,依賴注入的作用是實現類的自動實例化,而控制反轉里的容器實質是為了實現一個實例的復用,跟單例模式類似,但是更高級,比較專業的說法是*注冊樹模式*,這點在tp5.1里面有運用到,大家可以自行查閱tp5.1的源碼,控制反轉管理類是`container`類,容器則是里面的 `$instances` 屬性。篇幅關系,這里不再探討**
**其他參考**
如何理解php的依賴注入 http://www.cnblogs.com/phpper/p/7781119.html
理解 PHP 依賴注入 https://www.cnblogs.com/liliuguang/p/7825078.html
PHP控制反轉(IOC)和依賴注入(DI) https://www.cnblogs.com/sweng/p/6392336.html
PHP程序員如何理解IoC/DI https://segmentfault.com/a/1190000002411255
學習依賴注入與控制反轉 https://segmentfault.com/a/1190000010456858
PHP 依賴注入(DI) 和 控制反轉(IoC)實例教程 http://www.php.cn/php-weizijiaocheng-367767.html
- 序言
- 圖片預覽
- 詮釋高效開發
- 提問的智慧
- GIT命令參考
- 安裝composer
- 斷點調試技巧
- 調試環境的搭建
- 調試工具的使用及技巧
- 前置基礎-TP底層講解
- 理解編程的抽象
- 耦合與解耦
- 自動加載
- 反射類
- 控制反轉(IOC)和依賴注入(DI)
- iThink 自定義依賴注入的實現
- 常用設計模式
- SPL標準庫
- 行為-鉤子-插件
- AOP-面向切面
- RBAC和Auth類的本質
- 安裝iThink
- 環境要求
- 代碼下載與環境配置
- 執行安裝
- 體驗測試模塊
- apache配置
- nginx配置
- 系統架構詳解
- 目錄詳解
- 執行流程圖
- 數據字典
- RBAC 權限管理架構
- 系統分層詳解
- 控制器層(controller)
- 邏輯層(logic)
- 視圖層(view)
- 模型層(model)
- 服務層(service)
- 應用包架構詳解
- 目錄結構
- 開發規范
- 數據庫規范
- 編碼規范
- 功能設計原則與規范
- 后臺功能詳解
- 基礎功能
- RBAC + Auth 權限機制
- 應用化功能機制
- 代碼生成器(重要)
- 應用骨架代碼生成
- 數據表 CURD 代碼生成
- 頁面構造器(重要)
- 通用元素構造器
- 表格元素構造器
- 搜索表單元素構造器
- 表單元素構造
- 閉包事物構造器
- 應用的開發
- 函數參考