衆所周知,Laravel 控制反轉 (IoC) / 依賴注入 (DI) 的功能很是強大。遺憾的是, 官方文檔 並無詳細講解它的全部功能,因此我決定本身實踐一下,並整理成文。下面的代碼是基於 Laravel 5.4.26 的,其餘版本可能會有所不一樣。php
我在這裏不會詳細講解依賴注入/控制反轉的原則 - 若是你對此還不是很瞭解,建議閱讀 Fabien Potencier (Symfony 框架的創始人)的 What is Dependency Injection? 。html
經過 Laravel 訪問 Container 實例的方式有不少種,最簡單的就是調用輔助函數 app()
:laravel
爲了突出重點 Container 類,這裏就不贅述其餘方式了。git
注意: 官方文檔中使用的是 $this->app
而不是 $container
。github
(* 在 Laravel 應用中,Application 其實是 Container 的一個子類 ( 這也說明了輔助函數 app()
的由來 ),不過在這篇文章中我仍是將重點講解 Container 類的方法。)sql
想要不基於 Laravel 使用 Container,安裝 而後:shell
最簡單的用法是經過構造函數注入依賴類。數組
使用 Container 的 make()
方法實例化 MyClass
類:緩存
container 會自動實例化依賴類,因此上面代碼實現的功能就至關於:服務器
( 假設 AnotherClass
還有須要依賴的類 - 在這種狀況下,Container 會遞歸式地實例化全部的依賴。)
phper在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裏入手去提高,對此我整理了一些資料,包括但不限於:分佈式架構、高可擴展、高性能、高併發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階乾貨須要的能夠免費分享給你們須要的(點擊→)個人官方羣677079770
下面是一些基於 PHP-DI 文檔 的例子 - 將發送郵件與用戶註冊的代碼解耦:
經過 Container 類,咱們能夠輕鬆實現從接口到具體類到實例的過程。首先定義接口:
聲明實現接口的具體類,具體類還能夠依賴其餘接口( 或者是像上個例子中的具體類 ):
而後使用 bind()
方法把接口與具體類進行綁定:
最後,在 make()
方法中,使用接口做爲參數:
注意: 若是沒有將接口與具體類進行綁定操做,就會報錯:
這是由於 container 會嘗試實例化接口 ( new MyInterface
),這自己在語法上就是錯誤的。
可更換的緩存層:
也能夠與抽象類進行綁定:
或者將具體類與其子類進行綁定:
在使用 bind()
方法進行綁定操做時,若是某個類須要額外的配置,還經過閉包函數來實現:
每次帶着配置信息建立一個 MySQLDatabase 類的實例的時候( 下面後講到如何經過 Singletons 建立一個能夠共享的實例),都要用到 Database 接口。咱們看到閉包函數接收了 Container 的實例做爲參數,若是須要的話,還能夠用它來實例化其餘類:
還能夠經過閉包函數自定義要如何實例化某個類:
可使用 resolving()
方法來註冊一個回調函數,當綁定被解析的時候,就調用這個回調函數:
全部的註冊的回調函數都會被調用。這種方法也適用於接口和抽象類:
還能夠註冊一個任何類被解析時都會被調用的回調函數 - 可是我想這可能僅適用於登陸和調試:
你還可使用 extend()
方法把一個類與另外一個類的實例進行綁定:
這裏返回的另一個類應該也實現了一樣的接口,不然會報錯。
只要使用 bind()
方法進行綁定,每次用的時候,就會建立一個新的實例( 閉包函數就會被調用一次)。爲了共用一個實例,可使用 singleton()
方法來代替 bind()
方法:
或者是閉包:
爲一個具體類建立單例,就只傳這個類做爲惟一的參數:
在以上的每種狀況下,單例對象都是一次建立,反覆使用。若是想要複用的實例已經生成了,則可使用 instance()
方法。例如,Laravel 就是用這種方式來確保Container 的實例有且僅有一個的:
其實,你可使用任意字符串做爲綁定的名稱,而不必定非要用類名或者接口名 - 可是這樣作的弊端就是不能使用類名實例化了,而只能使用 make()
方法:
爲了同時支持類和接口,而且簡化類名的寫法,可使用 alias()
方法:
你也可使用 container 來存儲任何值 - 好比:配置數據:
支持以數組的形式存儲:
在經過閉包進行綁定的時候,這種存儲方式就顯示出其好用之處了:
( Laravel 框架沒有用 container 來存儲配置文件,而是用了單獨的 Config 類 - 可是 PHP-DI 用了)
小貼士: 在實例化對象的時候,還能夠用數組的形式來代替 make()
方法:
到目前爲止,咱們已經看了不少經過構造函數進行依賴注入的例子,其實,Laravel 還支持對任何方法作依賴注入:
除了依賴類,還能夠傳其餘參數:
可用於任何可調用的方法:
經過這種語法結構 ClassName@methodName
,就 能夠達到實例化一個類並調用其方法的目:
容器用於實例化類,這意味着:
例如,這將會啓做用:
最後,你能夠將「默認方法」做爲第三個參數。若是第一個參數是一個沒有指定方法的類名,則將調用默認的方法。 Laravel 使用 事件處理 來實現:
可使用 bindMethod()
方法重寫方法調用,例如傳遞其餘參數:
全部這些都會奏效,調用閉包而不是的原始方法:
可是, call()
的任何附加參數都不會傳遞到閉包中,所以不能使用它們。
注意: 這個方法不屬於 容器接口, 只是具體的 容器類. 參考 提交的 PR 瞭解爲何忽略參數。
有時候,你但願在不一樣的地方使用接口的不一樣實現。下面是來自 Laravel 文檔 中的一個例子:
如今, PhotoController 和 VideoController 均可以依賴於文件系統接口,可是每一個都將接收不一樣的實現。你還能夠爲 give()
使用閉包,就像使用 bind()
同樣:
或者命名依賴項:
你還能夠經過將變量名稱傳遞給 needs()
(而不是接口)並將值傳遞給 give()
來綁定基本類型(字符串,整數等):
您可使用閉包來延遲檢索值,直到須要它:
在這裏你不能傳遞一個類或一個命名的依賴項(例如 give('database.user')
)由於它將做爲文字值返回 - 爲此你必須使用一個閉包:
你可使用容器 tag
來綁定相關標記:
而後將全部標記的實例檢索爲數組:
tag()
的參數都接受數組:
*Note: 這是一個更高級的,只是不多須要-請隨意跳過它! *
在綁定或實例已經被使用後須要更改時,能夠調用 rebinding()
回調 - 例如,此 Session
類在被 Auth
類使用後被替換,所以須要通知 Auth
類變化:
還有一個快捷方法 refresh()
來處理這個常見模式:
它還返回現有實例或綁定(若是有的話),所以您能夠這樣作:
(就我的而言,我發現這種語法更加混亂,而且更喜歡上面更詳細的版本!)
Note: 這些方法不屬於 Container interface, 只有具體 Container class.
makeWith()
方法容許你將其餘參數傳遞給構造函數。 它忽略任何現有的實例或單例,而且在建立具備不一樣參數的類的多個實例時仍然有用,同時仍然注入依賴項:
Note: 在Laravel 5.3及如下版本中,它很簡單 make($class, $parameters)
. 它是在 Laravel 5.4 被移除, 但後來 從新添加爲 makeWith() 在 5.4.16. 在Laravel 5.5中,它彷佛將恢復爲Laravel 5.3語法.
這涵蓋了我認爲有用的全部方法 - 但只是爲了解決問題,這裏是剩下的公共方法的摘要......
若是類或名稱已與 bind()
, singleton()
, instance()
or alias()
綁定,則 bound()
返回true。
它能夠用 unset()
重置,它刪除指定的綁定/實例/別名。
bindIf()
與 bind()
作一樣的事情,除了它只註冊一個綁定(若是尚未)(參考上面的 bound()
)。 它可能用於在包中註冊默認綁定,同時容許用戶覆蓋它。
沒有 singletonIf()
方法,但你可使用 bindIf($abstract, $concrete, true)
代替:
或者這樣寫全也能夠:
若是已經解析了類 resolved()
則返回true。
我不肯定它有什麼用處,若是使用 unset()
它會被重置 (能夠看上面的 bound()
)。
factory()
方法返回一個不帶參數的閉包,並調用 make()
。
我不肯定它有什麼用處...
wrap()
方法包裝一個閉包,以便在執行時注入它的依賴項。 wrap 方法接受一組參數, 返回的閉包沒有參數:
我不肯定它有什麼用處,由於閉包沒有參數...
Note: 這種方法不屬於 Container interface, 只屬於 Container class.
afterResolving()
方法與 resolving()
徹底相同,只是在「解析」回調以後調用 「解析後」 回調。 我不肯定何時會有用...
isShared()
- 肯定給定類型是否爲共享單例/實例isAlias()
- 肯定給定字符串是不是已註冊的別名hasMethodBinding()
- 肯定容器是否具備給定的方法綁定getBindings()
- 檢索全部已註冊綁定的原始數組getAlias($abstract)
- 解析基礎類/綁定名稱的別名forgetInstance($abstract)
- 清除單個實例對象forgetInstances()
- 清除全部實例對象flush()
- 清除全部綁定和實例,有效地重置容器setInstance()
- 替換 getInstance()
使用的實例(Tip:使用 setInstance(null)
清除它,因此下次它將生成一個新實例)Note: 最後一節中沒有一個方法是其中的一部分 Container interface.