# laravel DI(依賴注入) IOC(依賴反轉)(控制反转) https://learnku.com/articles/56111 原生必看 https://www.bilibili.com/video/BV17Q4y1M7Cg?from=search&seid=12003072008765508472&spm_id_from=333.337.0.0 https://medium.com/mr-efacani-teatime/laravel%E5%A6%82%E4%BD%95%E5%AF%A6%E7%8F%BE%E4%BE%9D%E8%B3%B4%E6%80%A7%E6%B3%A8%E5%85%A5-d760c8e5abde https://dotblogs.com.tw/daniel/2018/01/17/140435 https://notfalse.net/3/ioc-di https://laravel.tw/docs/4.2/ioc https://learnku.com/articles/6248/laravel-inversion-of-control-control-inversion-concept-brief-introduction 不錯 https://notfalse.net/3/ioc-di#-Constructor-Injection-vs-Setter-Injection 英文的挺好理解di https://laracasts.com/series/laravel-from-scratch-2018/episodes/21 必看 https://learnku.com/laravel/t/10313/how-to-understand-the-service-container-bind-make-method-and-auxiliary-function-app-method 幻寫的 https://blog.twjoin.com/laravel-%E8%87%AA%E5%8B%95%E4%BE%9D%E8%B3%B4%E6%B3%A8%E5%85%A5-54bb7a3e4ea0 ## 重點觀念 https://www.youtube.com/watch?v=y7EbrV4ChJs 歐洲講解非常關鍵 最簡單container ![](https://i.imgur.com/H22nJfS.png) 如果嵌套會很多層 ![](https://i.imgur.com/Bs8lc7g.png) 但因為你有return This 可以這樣 ![](https://i.imgur.com/Aw2IDEC.jpg) 簡化成 ![](https://i.imgur.com/5hwVKGw.png) 進階成autowiring 最後自己跑foreach抓取自己相關的 ![](https://i.imgur.com/JHYgnpx.jpg) 最後幫她newInstance ![](https://i.imgur.com/xAb7Txe.jpg) **Laravel藉由Service Container與Service Provider實現了依賴性注入** 控制反轉(IOC)是一種思想,依賴注入(DI)是實施這種思想的方法 **注意,不一定要一起使用,技術長是跟我說serice這種不算是ioc,laravel原生的才是** IOC 主要觀念在於用interface減少耦合 DI注入interface,而不是class ,但平常不用這樣用,平常就直接用DI就好,所以DI跟IOC是分開的 (有街口的話要去註冊,註冊街口) ![](https://i.imgur.com/XuY5u8S.png) 平常直接用class注入,是使用反射的原理去解析 --- 在说 Ioc 容器之前,我们需要了解什么是 Ioc 容器。 Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。 在理解这句话之前,我们需要先了解一下服务容器的来龙去脉: laravel神奇的服务容器。这篇博客告诉我们,服务容器就是工厂模式的升级版,对于传统的工厂模式来说,虽然解耦了对象和外部资源之间的关系,但是工厂和外部资源之间却存在了耦和。而服务容器在为对象创建了外部资源的同时,又与外部资源没有任何关系,这个就是 Ioc 容器。   所谓的依赖注入和控制反转: 依赖注入和控制反转,就是 只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI) 也就是说: 依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源; 控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。 ## IoC容器案例,理解什么是依赖注入和控制反转 https://learnku.com/articles/56111 ## controller的依賴注入 https://www.jb51.net/article/133342.htm https://www.ktanx.com/blog/p/5017 ### app() resolve() https://www.coder.work/article/331597 https://liseen315.gitbook.io/laravel/rong-qi/app-quan-ju-fu-zhu-han-shu 必看 https://learnku.com/laravel/t/10313/how-to-understand-the-service-container-bind-make-method-and-auxiliary-function-app-method app() 和 $this->app 是同一个东西 (Ioc容器). 要用 $this->app 肯定是当前类有 app 这个成员变量才行,而 app() 是直接从容器的静态变量去取的.app() 在文档中被描述为帮助函数,目的就是在你不能 $this->app 时使用。 composer 的自动加载,你完全可以在框架的任何地方直接 new 项目中的类。如果你没有注册,那么 app(Some::class) 就是帮你 new 一个对象而已。有些类在创建时,需要一些参数,这时候,你就可以注册到容器中。你在把类注册到容器时,你是在告诉容器,这个类应该怎么 new resolve() 就只是容器的解析make而已 ## DI **把被依賴物件注入被動接收物件中** 原理是反射 依賴注入(DI) ( Dependency injection ) 是一種移除 hard-coded 類別依賴的方式。相較之下,在執行的時候才注入依賴, 可以擁有更好的彈性,在替換依賴實體時相當容易。 核心概念:簡單來說就是 class 內部不會用 new 的方式去實例化依賴的服務, 而是由外部 interface 通過 Dependency Injection(依賴注入)的方式獲取實例 如果依賴的是具體的類別(concrete class),則服務容器會透過PHP的Reflection機制,自動取得依賴實例 **依賴性注入是先取得依賴類別的實例(instance),再將其注入到欲使用依賴的類別裡。** **依賴介面還是類別? 大多數情況,我們認為依賴介面是最佳實作,而Laravel可以透過容器來取得依賴的實例,如果依賴的是具體的類別(concrete class),則服務容器會透過PHP的Reflection機制,自動取得依賴實例,官方文件有特別提示:** (如果要綁定的類別不是介面,而是一般的類別,容器是不需要被指示要如何建立物件的,它會利用reflection自動解析物件。) 如果依賴的是介面(interface),為了取得依賴類別的實例,必須先告訴服務容器這個介面要綁定哪個具體的類別,這個動作就是所謂的註冊綁定(register a binding),而“綁定(binding)”其實就是在容器的綁定列表$bindings裡加入鍵值對(key-value pairs),讓$app可以藉由鍵(key)找到對應的依賴實例。例如: ``` /* $app->bind(key, value) */ $app->bind(App\Library\DependencyInterface::class, function($app){ return new App\Library\DependencyImplement(); } ``` 在服務容器裡完成綁定後,我們不需要實例化,也可以直接在Controller中使用依賴實例$dependency的doSomething()方法: ``` class DemoController extends Controller { public function __construct(App\Library\DependencyInterface $dependency) { // using App\Library\DependencyImplement()->doSomething() $dependency->doSomething(); } ``` https://laracasts.com/series/laravel-from-scratch-2018/episodes/21 其實就是做app() 做 ## IOC https://www.itread01.com/content/1504719487.html DI是單純的依賴 IOC是 有一個inerface是插頭 然後有三個class實現這interface(單插頭,雙插頭,三插頭) 這三個插頭都能給設備用(用DI)(**注入interface 而不是class**) 實例化的時候再決定使用哪種插座類型,這樣依賴就轉移到了外部 **IOC要有interface 然後DI是用interface** 不清楚看上面文章 **IoC經典實現對象設計法則 好萊塢法則:“別找我們,我們找你”** **系統中模組建議依賴抽象,因為各個模組間不需要知道對方太多細節(實作),知道越多耦合越強。** 我們直接在對象內部通過new進行創建對象,是程序主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對 象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什么?那就是主要控制了外部資源獲取 Ioc—Inversion of Control 控制反轉 控制反轉是一個設計思想 ,把對於某個物件的控制權移轉給第三方容器 簡單解釋 A物件程式內部需要使用B物件 A,B物件中有依賴的成份 控制反轉是把原本A對B控制權移交給第三方容器 降低A對B物件的耦合性,讓雙方都倚賴第三方容器。 ![](https://i.imgur.com/p6zsIuk.png) 实现「控制反转」,有两种方式: Dependency Injection (DI) - 依赖注入 Dependency Lookup - 依赖查找 Interface介面好像也是這種概念(在JAVA文章看到的)(認同) **两者的区别在于,前者是被动的接收对象,在类实例创建过程中即创建了依赖的对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。** 控制反轉就是為了解決這種依賴問題, 而衍生出來的設計模式 而要怎麼反轉呢? 就是靠與依賴注入來實現 ## 基本用法 **IoC 容器有兩種解析依賴的方式:經由閉合函數或自動解析** ### 綁定型別到容器 ``` App::bind('foo', function($app) { return new FooBar; }); ``` ### 從容器解析型別 `$value = App::make('foo');` 當 App::make 方法被呼叫,綁定的閉合函數會被呼叫並返回結果 ### 綁定「共享」的型別到容器 綁定到容器的型別只會被解析一次,之後的呼叫都返回相同的實例 ``` App::singleton('foo', function() { return new FooBar; }); ``` ### 綁定已存在的實例到容器 使用 instance 方法,綁定一個已經存在的實例到容器 $foo = new Foo; App::instance('foo', $foo); ## 註冊綁定 IoC 綁定跟「註冊事件處理」或是「註冊路由」一樣,通常稱為「起始碼」。換句話說,IoC 綁定後等待請求,在路由或控制器呼叫時才實際執行。像其他的「起始碼」一樣,start 檔案總是註冊 IoC 綁定的一個選擇。或者,你可以建立一個 app/ioc.php 檔案(檔名不重要),並且從 start 檔案引入。 如果你的應用程式有很多 IoC 綁定,或是想要分門別類,在不同檔案組織綁定,你可以註冊綁定在 服務提供者( Service Provider )。 ## 自動解析 在很多情境下,IoC 容器不需要額外設定就有能力自動解析類別。例如: ``` class FooBar { public function __construct(Baz $baz) { $this->baz = $baz; } } $fooBar = App::make('FooBar'); ``` 雖然我們沒有註冊 FooBar 類別綁定到容器,它還是可以解析類別,甚至自動注入 Baz! 如果容器裡沒有找到對應的型別綁定,容器會利用 PHP 的 Reflection (反射)檢查類別,並且解讀傳入建構子的型別提示。利用這些資訊,讓容器可以自動建立類別實例。 ## 綁定實例的介面 一個類別可能需要依賴介面,而不是一個「具體的型別」。這些情況下,App::bind 方法用來通知容器要注入哪個介面實例: `App::bind('UserRepositoryInterface', 'DbUserRepository');` 控制器 ``` class UserController extends BaseController { public function __construct(UserRepositoryInterface $users) { $this->users = $users; } } ``` 既然我們已經綁定 UserRepositoryInterface 到一個具體型別,DbUserRepository 會在控制器建立時自動注入。 ### 小結 Laravel將綁定分成了兩種類型: * 實例(instances)綁定:綁定某個類別的實例,所有綁定的實例會記錄在Illuminate\Container->$instances裡。 * 函數(functions)綁定:綁定某個回調函數方法,回調函數又分成單例綁定和一般綁定。單例綁定是singleton design pattern的實踐,也就是說在程式的整個生命週期中,只會生成一個綁定類別的實例,並且共用這個實例;普通綁定則是在程式的整個生命週期中會生成好幾個綁定類別的實例。 ## 應用 **在 Laravel 裡,很多時候使用 IoC 容器,可以增加應用程式的彈性與可測試性。一個基本的範例是用在解析控制器。所有的控制器都會經由 IoC 容器解析,意味著你可以在控制器的建構子注入型別提示依賴,之後依賴就會自動被注入。** **注入型別提示 ( Type-Hinting ) 依賴到控制器** ``` class OrderController extends BaseController { public function __construct(OrderRepository $orders) { $this->orders = $orders; } public function getIndex() { $all = $this->orders->all(); return View::make('orders', compact('all')); } } ``` 在這個範例裡,OrderRepository 類別會自動被注入到控制器。意味著在 單元測試 時,可以綁定一個「mock」的 OrderRepository 到容器裡,之後被注入到 控制器,讓你不用在測試時一定要和資料庫層互動。 ## 其他 Ioc 應用範例 Route::filter('foo', 'FooFilter'); View::composer('foo', 'FooComposer'); Event::listen('foo', 'FooHandler'); ## 服務提供者( Service Provider )(IoC 註冊) 使用服務提供者是一個很好的方式,可以把相關的 IoC 註冊放到到同一個地方。可以將服務提供者想像成是一個在應用程式裡啟動元件的方式。你可以在裡面註冊自定的會員認證,綁定應用程式的儲存庫類別到 IoC 容器,或甚至設定自定的 Artisan 指令。 事實上,大部份的 Laravel 核心元件都有服務提供者,所有被註冊的服務提供者都列在 **app/config/app.php 設定檔的 providers 陣列裡。** ### 定義一個服務提供者 要建立一個服務提供者,只要繼承 Illuminate\Support\ServiceProvider 類別,然後在裡面定義一個 register 方法: ``` use Illuminate\Support\ServiceProvider; class FooServiceProvider extends ServiceProvider { public function register() { $this->app->bind('foo', function() { return new Foo; }); } } ``` 注意,在 register 方法裡,經由 $this->app 使用 IoC 容器。當你建立了一個服務 而且準備要註冊到你的應用程式裡時,只要把它加到你的 app 設定檔的 providers 陣列裡即可。 ### 在執行期間註冊服務提供者 你也可以使用 App::register 在執行期間註冊服務提供者: `App::register('FooServiceProvider')`; ### 容器事件 註冊解析事件的監聽 **每當容器解析一個物件時就會觸發事件,你可以使用 resolving 方法監聽這個事件:** ``` App::resolvingAny(function($object) { // }); App::resolving('foo', function($foo) { // }); ``` **注意,被解析的物件會被傳到閉合函式。** ###### tags: `Laravel`