本文面向php語(yǔ)言的laravel框架的用戶,介紹一些laravel框架里面容器管理方面的使用要點(diǎn)。文章很長(zhǎng),但是內(nèi)容應(yīng)該很有用,希望有需要的朋友能看到。php經(jīng)驗(yàn)有限,不到位的地方,歡迎幫忙指正。

1. laravel容器基本認(rèn)識(shí)

laravel框架是有一個(gè)容器框架,框架應(yīng)用程序的實(shí)例就是一個(gè)超大的容器,這個(gè)實(shí)例在bootstrap/app.php內(nèi)進(jìn)行初始化:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

這個(gè)文件在每一次請(qǐng)求到達(dá)laravel框架都會(huì)執(zhí)行,所創(chuàng)建的$app即是laravel框架的應(yīng)用程序?qū)嵗?,它在整個(gè)請(qǐng)求生命周期都是唯一的。laravel提供了很多服務(wù),包括認(rèn)證,數(shù)據(jù)庫(kù),緩存,消息隊(duì)列等等,$app作為一個(gè)容器管理工具,負(fù)責(zé)幾乎所有服務(wù)組件的實(shí)例化以及實(shí)例的生命周期管理。這種方式能夠很好地對(duì)代碼進(jìn)行解耦,使得應(yīng)用程序的業(yè)務(wù)代碼不必操心服務(wù)組件的對(duì)象從何而來(lái),當(dāng)需要一個(gè)服務(wù)類來(lái)完成某個(gè)功能的時(shí)候,僅需要通過(guò)容器解析出該類型的一個(gè)實(shí)例即可。從最終的使用方式來(lái)看,laravel容器對(duì)服務(wù)實(shí)例的管理主要包括以下幾個(gè)方面:

  • 服務(wù)的綁定與解析

  • 服務(wù)提供者的管理

  • 別名的作用

  • 依賴注入

弄清這幾個(gè)方面的思想, 以及l(fā)aravel容器的實(shí)現(xiàn)機(jī)制,就能熟練掌握l(shuí)aravel容器的管理。

2. 如何在代碼中獲取到容器實(shí)例

laravel容器實(shí)例在整個(gè)請(qǐng)求生命周期中都是唯一的,且管理著所有的服務(wù)組件實(shí)例。那么有哪些方式能夠拿到laravel容器的實(shí)例呢?常用的有以下幾種方式:

1) 通過(guò)app這個(gè)help函數(shù):

+ View code

app這個(gè)輔助函數(shù)定義在 
photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn) 
文件里面,這個(gè)文件定義了很多help函數(shù),并且會(huì)通過(guò)composer自動(dòng)加載到項(xiàng)目中。所以,在參與http請(qǐng)求處理的任何代碼位置都能夠訪問(wèn)其中的函數(shù),比如app()。

2)通過(guò)App這個(gè)Facade

+ View code

通過(guò)App這個(gè)Facade拿容器實(shí)例的方式,跟上面不同的是,不能把App先賦給一個(gè)變量,然后通過(guò)變量來(lái)調(diào)用容器的方法。這是因?yàn)锳pp相當(dāng)于只是一個(gè)類名,我們不能把一個(gè)類名復(fù)制一個(gè)變量。$app = App;不是一個(gè)合法的可執(zhí)行的語(yǔ)句,而$app = app();卻是一個(gè)合法的可執(zhí)行的語(yǔ)句,因?yàn)樗竺嬗衋pp(),表示函數(shù)調(diào)用。App::basePath();也是一個(gè)合法的語(yǔ)句,它就是在調(diào)用類的靜態(tài)方法。

再補(bǔ)充2點(diǎn):

第一點(diǎn): Facade是laravel框架里面比較特殊的一個(gè)特性,每個(gè)Facade都會(huì)與容器里面的一個(gè)實(shí)例對(duì)象關(guān)聯(lián),我們可以直接通過(guò)Facade類靜態(tài)方法調(diào)用的形式來(lái)調(diào)用它關(guān)聯(lián)的實(shí)例對(duì)象的方法。比如App這個(gè)Facade,調(diào)用App::basePath()的時(shí)候,實(shí)際相當(dāng)于app()->basePath()。這個(gè)底層機(jī)制也是依賴于php語(yǔ)言的特性才能實(shí)現(xiàn)的,需要在每一個(gè)Facade里面,設(shè)定一個(gè)靜態(tài)成員并關(guān)聯(lián)到一個(gè)服務(wù)的實(shí)例對(duì)象,當(dāng)調(diào)用Facade類的靜態(tài)方法的時(shí)候,解析出調(diào)用的方法名,再去調(diào)用關(guān)聯(lián)的服務(wù)實(shí)例的同名方法,最后把結(jié)果返回。我認(rèn)為理解Facade能起到什么作用就夠了,不一定要深究到它底層去了解實(shí)現(xiàn)的細(xì)節(jié),畢竟在實(shí)際的開(kāi)發(fā)中,不用Facade,也完全不影響laravel框架的使用。另外在實(shí)際編碼中,要自定義一個(gè)Facade也非常容易,只要繼承l(wèi)aravel封裝的Facade基類即可:

+ View code

實(shí)現(xiàn)Facade基類的getFacadeAccessor方法,laravel框架就知道這個(gè)Facade類該與哪個(gè)服務(wù)實(shí)例關(guān)聯(lián)起來(lái)了。實(shí)際上這個(gè)getFacadeAccess方法,返回的名稱就是后面要介紹的服務(wù)綁定名稱。在laravel容器里面,一個(gè)服務(wù)實(shí)例,都會(huì)有一個(gè)固定的綁定名稱,通過(guò)這個(gè)名稱就能找到這個(gè)實(shí)例。所以為啥Facade類只要返回服務(wù)綁定名稱即可。

我們可以看看App這個(gè)Facade類的代碼:

+ View code

它的getFacadeAccessor返回的就是一個(gè)字符串“app”,這個(gè)app就是laravel容器自己綁定自己時(shí)用的名稱。

第二點(diǎn): 從上一點(diǎn)最后App這個(gè)Facade的源碼可以看出,App這個(gè)Facade的全類名其實(shí)是:Illuminate\Support\Facades\App,那為什么我們?cè)诖a里面能夠直接通過(guò)App這個(gè)簡(jiǎn)短的名稱就能訪問(wèn)到呢:

+ View code

你看以上代碼完全沒(méi)有用到use或者完全限定的方式來(lái)使用Illuminate\Support\Facades\App。實(shí)際上App跟Illuminate\Support\Facades\App是完全等價(jià)的,只不過(guò)App比Illuminate\Support\Facades\App要簡(jiǎn)短很多,而且不需要use,所以用起來(lái)方便,那么它是怎么實(shí)現(xiàn)的?這跟laravel容器配置的別名有關(guān)系,在config/app.php中,有一節(jié)aliases專門用來(lái)配置一些類型的別名:

+ View code

然后在laravel框架處理請(qǐng)求過(guò)程中,會(huì)通過(guò)Illuminate\Foundation\Bootstrap\RegisterFacades這個(gè)類來(lái)注冊(cè)這些別名到全局環(huán)境里面:

+ View code

所以我們才能直接通過(guò)別名,代替完整的類型名做同樣的訪問(wèn)功能。如果你自己寫(xiě)了一些類,名稱很長(zhǎng),并且在代碼里面用的特別多,也可以考慮配置到config/app.php別名里面去,laravel會(huì)幫我們注冊(cè)。

3)另外一種方式拿到laravel容器實(shí)例就是在服務(wù)提供者里面直接使用$this->app

服務(wù)提供者后面還會(huì)介紹,現(xiàn)在只是引入。因?yàn)榉?wù)提供者類都是由laravel容器實(shí)例化的,這些類都繼承自Illuminate\Support\ServiceProvider,它定義了一個(gè)實(shí)例屬性$app:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

laravel在實(shí)例化服務(wù)提供者的時(shí)候,會(huì)把laravel容器實(shí)例注入到這個(gè)$app上面。所以我們?cè)诜?wù)提供者里面,始終能通過(guò)$this->$app訪問(wèn)到laravel容器實(shí)例,而不需要再使用app()函數(shù)或者App Facade了。

3. 直觀的認(rèn)識(shí)laravel容器

一直在說(shuō)容器,既然它是用來(lái)存取實(shí)例對(duì)象的時(shí)候,那么它里面應(yīng)該至少有一個(gè)數(shù)組充當(dāng)容器存儲(chǔ)功能的角色才行,所以我們可以通過(guò)打印的方式來(lái)直觀地看下laravel容器實(shí)例的結(jié)構(gòu):

+ View code

結(jié)果如下: 
photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn) 
從這個(gè)結(jié)構(gòu)可以看出,laravel容器實(shí)例上包含了很多的數(shù)組,其中紅框部分的數(shù)組,從名字也可以猜測(cè)出它們跟后面要介紹的服務(wù),服務(wù)提供者與服務(wù)別名之間的聯(lián)系。理清這幾個(gè)數(shù)組的存儲(chǔ)結(jié)構(gòu),自然就明白了laravel容器如何管理服務(wù)。

4. 如何理解服務(wù)綁定與解析

淺義層面理解,容器既然用來(lái)存儲(chǔ)對(duì)象,那么就要有一個(gè)對(duì)象存入跟對(duì)象取出的過(guò)程。這個(gè)對(duì)象存入跟對(duì)象取出的過(guò)程在laravel里面稱為服務(wù)的綁定與解析。

先來(lái)看服務(wù)綁定,在laravel里面,服務(wù)綁定到容器,有多種形式:

+ View code

singleton是laravel服務(wù)綁定的方法之一,詳細(xì)作用后面會(huì)介紹,目前只是用它來(lái)展現(xiàn)服務(wù)綁定的形式。籠統(tǒng)的說(shuō)容器的時(shí)候,我們說(shuō)容器管理的是服務(wù)對(duì)象,但是laravel的容器可以管理不僅僅是對(duì)象,它能夠管理的是任意類型的數(shù)據(jù),包括基本數(shù)據(jù)類型和對(duì)象。所以在服務(wù)綁定的時(shí)候,我們也可以綁定任意的數(shù)據(jù),正如以上代碼展示的那樣。在綁定的時(shí)候,我們可以直接綁定已經(jīng)初始化好的數(shù)據(jù)(基本類型、數(shù)組、對(duì)象實(shí)例),還可以用匿名函數(shù)來(lái)綁定。用匿名函數(shù)的好處在于,這個(gè)服務(wù)綁定到容器以后,并不會(huì)立即產(chǎn)生服務(wù)最終的對(duì)象,只有在這個(gè)服務(wù)解析的時(shí)候,匿名函數(shù)才會(huì)執(zhí)行,此時(shí)才會(huì)產(chǎn)生這個(gè)服務(wù)對(duì)應(yīng)的服務(wù)實(shí)例。

實(shí)際上,當(dāng)我們使用singleton,bind方法以及數(shù)組形式,(這三個(gè)方法是后面要介紹的綁定的方法),進(jìn)行服務(wù)綁定的時(shí)候,如果綁定的服務(wù)形式,不是一個(gè)匿名函數(shù),也會(huì)在laravel內(nèi)部用一個(gè)匿名函數(shù)包裝起來(lái),這樣的話, 不輪綁定什么內(nèi)容,都能做到前面介紹的懶初始化的功能,這對(duì)于容器的性能是有好處的。這個(gè)可以從bind的源碼中看到一些細(xì)節(jié):

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

服務(wù)綁定時(shí)的第一個(gè)參數(shù)就是服務(wù)的綁定名稱。服務(wù)綁定完成后,容器會(huì)把這個(gè)服務(wù)的綁定記錄存儲(chǔ)到實(shí)例屬性bindings里面:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

這個(gè)bindings里面的每條記錄,代表一個(gè)服務(wù)綁定。它的key值就是服務(wù)的綁定名稱,value值也是一個(gè)數(shù)組,這個(gè)數(shù)組的concrete屬性就是服務(wù)綁定時(shí)產(chǎn)生的匿名函數(shù),也就是閉包;另外一個(gè)參數(shù)表示這個(gè)服務(wù)在多次解析的時(shí)候,是否只返回第一次解析得到的對(duì)象。這個(gè)參數(shù)在介紹服務(wù)綁定方法時(shí)會(huì)再繼續(xù)介紹。

接下來(lái)看看服務(wù)綁定的幾種方法及區(qū)別:

a. 通過(guò)bind方法

+ View code

bind是laravel服務(wù)綁定的底層方法,它的簽名是:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

第一個(gè)參數(shù)服務(wù)綁定名稱,第二個(gè)參數(shù)服務(wù)綁定的結(jié)果,第三個(gè)參數(shù)就表示這個(gè)服務(wù)是否在多次解析的時(shí)候,始終返回第一次解析出的實(shí)例。它的默認(rèn)值是false,意味著這樣的服務(wù)在每次解析的時(shí)候都會(huì)返回一個(gè)新的實(shí)例。它的值與bindings里面服務(wù)綁定記錄value數(shù)組里面的share屬性是對(duì)應(yīng)的。

b. 通過(guò)singleton方法

舉例略。它跟bind的區(qū)別在于,它始終是以shared=true的形式進(jìn)行服務(wù)綁定,這是因?yàn)樗脑创a是這樣的:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

c. 通過(guò)數(shù)組的形式

+ View code

為什么可以直接把容器實(shí)例直接當(dāng)成數(shù)組來(lái)用呢,這是因?yàn)槿萜鲗?shí)現(xiàn)了php的ArrayAccess接口:

+ View code

所以實(shí)際上以上這種數(shù)組形式的綁定實(shí)際上相當(dāng)于沒(méi)有第三個(gè)參數(shù)的bind方法。

再來(lái)看服務(wù)的解析。上面的內(nèi)容都是在說(shuō)明把如何獲取服務(wù)實(shí)例的方式綁定到容器,那么如何從容器獲取到需要的服務(wù)實(shí)例呢?這個(gè)過(guò)程就是服務(wù)解析,在laravel里面通過(guò)make方法來(lái)完成服務(wù)的解析:

+ View code

這個(gè)方法接收兩個(gè)參數(shù),第一個(gè)是服務(wù)的綁定名稱和服務(wù)綁定名稱的別名,如果是別名,那么就會(huì)根據(jù)服務(wù)綁定名稱的別名配置,找到最終的服務(wù)綁定名稱,然后進(jìn)行解析;第二個(gè)參數(shù)是一個(gè)數(shù)組,最終會(huì)傳遞給服務(wù)綁定產(chǎn)生的閉包。

我們可以通過(guò)make的源碼理解服務(wù)解析的邏輯,這個(gè)是Illuminate\Container\Container類中的make方法源碼,laravel的容器實(shí)例是Illuminate\Foundation\Application類的對(duì)象,這個(gè)類繼承了Illuminate\Container\Container,這里暫時(shí)只展示Illuminate\Container\Container類中的make方法的代碼,先不涉及Illuminate\Foundation\Application類的make方法,因?yàn)楹笳吒采w了Illuminate\Container\Container類中的make方法,加了一些服務(wù)提供者的邏輯,所以這里先不介紹它。其實(shí)前面的很多源碼也都是從Illuminate\Container\Container中拿出來(lái)的,不過(guò)那些代碼Application沒(méi)有覆蓋,不影響內(nèi)容的介紹。

+ View code

從這個(gè)源碼可以看到:

a. 在解析一個(gè)服務(wù)的時(shí)候,它會(huì)先嘗試把別名轉(zhuǎn)換成有效的服務(wù)綁定名稱;

b. 如果這個(gè)服務(wù)是一個(gè)shared為true的服務(wù)綁定,且之前已經(jīng)做過(guò)解析的話,就會(huì)直接返回之前已經(jīng)解析好的對(duì)象;

c. 如果這個(gè)服務(wù)是一個(gè)shared為true的服務(wù)綁定,并且是第一次解析的話,就會(huì)把已解析的對(duì)象存入到instances這個(gè)容器屬性里面去,也就是說(shuō)只有shared為true的服務(wù)綁定,在解析的時(shí)候才會(huì)往instances屬性里面存入記錄,否則不會(huì)存入;

d. 解析完畢,還會(huì)在容器的resolved屬性里面存入一條記錄,表示這個(gè)服務(wù)綁定解析過(guò);

e. resolved,instances數(shù)組的key值跟bindings數(shù)組的key值一樣,都是服務(wù)綁定名稱;

f. 服務(wù)綁定的shared屬性在整個(gè)服務(wù)綁定生命周期內(nèi)都是不能更改的。

服務(wù)的解析也有多種形式,常用的有:

a. make方法

b. 數(shù)組形式

+ View code

這個(gè)的原理還是跟容器實(shí)現(xiàn)了ArrayAccess的接口有關(guān)系:

+ View code

所以數(shù)組形式的訪問(wèn)跟不使用第二個(gè)參數(shù)的make方法形式是一樣的。

c. app($service)的形式

+ View code

看了app這個(gè)help函數(shù)的源碼就明白了:

+ View code

原來(lái)app這個(gè)函數(shù)在第一個(gè)參數(shù)為空的時(shí)候,返回的是容器實(shí)例本身。在有參數(shù)的時(shí)候等價(jià)于調(diào)用容器實(shí)例的make方法。

以上就是服務(wù)綁定與解析的主要內(nèi)容,涉及的要點(diǎn)較多,希望描述的比較清楚。

5. 服務(wù)提供者的作用與使用

前面介紹了服務(wù)的綁定。那么服務(wù)的綁定應(yīng)該在哪個(gè)位置處理呢?雖然說(shuō),能夠拿到容器實(shí)例的地方,就都能進(jìn)行服務(wù)的綁定;但是我們使用服務(wù)的綁定的目的,是為了在合適的位置解析出服務(wù)實(shí)例并使用,如果服務(wù)綁定的位置過(guò)于隨意,那么就很難保證在解析的位置能夠準(zhǔn)確的解析出服務(wù)實(shí)例。因?yàn)榉?wù)能夠解析的前提是服務(wù)綁定的代碼先與服務(wù)解析的代碼執(zhí)行;所以,服務(wù)綁定通常會(huì)在應(yīng)用程序初始化的時(shí)候進(jìn)行,這樣才能保證業(yè)務(wù)代碼中(通常是router和controller里面)一定能解析出服務(wù)實(shí)例。這個(gè)最佳的位置就是服務(wù)提供者。

服務(wù)提供者,在laravel里面,其實(shí)就是一個(gè)工廠類。它最大的作用就是用來(lái)進(jìn)行服務(wù)綁定。當(dāng)我們需要綁定一個(gè)或多個(gè)服務(wù)的時(shí)候,可以自定義一個(gè)服務(wù)提供者,然后把服務(wù)綁定的邏輯都放在該類的實(shí)現(xiàn)中。在larave里面,要自定一個(gè)服務(wù)提供者非常容易,只要繼承Illuminate\Support\ServiceProvider這個(gè)類即可。下面通過(guò)一個(gè)簡(jiǎn)單的自定義服務(wù)提供者來(lái)說(shuō)明服務(wù)提供者的一些要點(diǎn):

+ View code

1). 首先,自定義的服務(wù)提供者都是放在下面這個(gè)目錄的: 
photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn) 
其實(shí)你放在哪都可以,不過(guò)得告訴laravel你的服務(wù)提供者在哪,laravel才會(huì)幫你注冊(cè)。怎么告訴它,后面還有介紹。

2)在這個(gè)舉例里面,可以看到有一個(gè)register方法,這個(gè)方法是ServiceProvider里面定義的。自定義的時(shí)候,需要重寫(xiě)它。這個(gè)方法就是用來(lái)綁定服務(wù)的。你可以在這個(gè)服務(wù)里面,根據(jù)需要加入任意數(shù)量的服務(wù)綁定。前面要介紹過(guò),在服務(wù)提供者里面,始終能通過(guò)$this->app拿到容器實(shí)例,所以上面的舉例中,我們直接用這種方式來(lái)完成服務(wù)綁定。這個(gè)方法是怎么完成服務(wù)綁定的呢?因?yàn)楫?dāng)laravel找到這個(gè)服務(wù)提供者的類以后,就會(huì)初始化這個(gè)服務(wù)提供者類,得到一個(gè)服務(wù)提供者的對(duì)象,然后調(diào)用它的register方法,自然它里面的所有服務(wù)綁定代碼就都會(huì)執(zhí)行了。

laravel初始化自定義服務(wù)提供者的源碼是:

+ View code

這個(gè)代碼是在Illuminate\Foundation\Application的源碼里面拿出來(lái)的,從中你能看到laravel會(huì)把所有的自定義服務(wù)提供者都注冊(cè)進(jìn)來(lái)。這個(gè)注冊(cè)的過(guò)程其實(shí)就是前面說(shuō)的實(shí)例化服務(wù)提供者的類,并調(diào)用register方法的過(guò)程。

3). 從上一步的源碼也能看到,laravel加載自定義服務(wù)提供者的時(shí)候,實(shí)際是從config/app.php這個(gè)配置文件里面的providers配置節(jié)找到所有要注冊(cè)的服務(wù)提供者的。

+ View code

所以你如果自己寫(xiě)了一個(gè)服務(wù)提供者,那么只要配置到這里面,laravel就會(huì)自動(dòng)幫你注冊(cè)它了。

4)除了register方法,服務(wù)提供者里面還有一個(gè)boot方法,這個(gè)boot方法,會(huì)在所有的服務(wù)提供者都注冊(cè)完成之后才會(huì)執(zhí)行,所以當(dāng)你想在服務(wù)綁定完成之后,通過(guò)容器解析出其它服務(wù),做一些初始化工作的時(shí)候,那么就可以這些邏輯寫(xiě)在boot方法里面。因?yàn)閎oot方法執(zhí)行的時(shí)候,所有服務(wù)提供者都已經(jīng)被注冊(cè)完畢了,所以在boot方法里面能夠確保其它服務(wù)都能被解析出來(lái)。

5)前面說(shuō)的服務(wù)提供者的情況,在laravel應(yīng)用程序初始化的時(shí)候,就會(huì)去注冊(cè)服務(wù)提供者,調(diào)用register方法。但是還有一種需求,你可能需要在真正用到這個(gè)服務(wù)提供者綁定的服務(wù)的時(shí)候,才會(huì)去注冊(cè)這個(gè)服務(wù)提供者,以減少不必要的注冊(cè)處理,提高性能。這也是延遲處理的一種方式。那么這種服務(wù)提供者該怎么定義呢?

其實(shí)最前面的這個(gè)舉例已經(jīng)告訴你了,只要定義一個(gè)$defer的實(shí)例屬性,并把這個(gè)實(shí)例屬性設(shè)置為true,然后添加一個(gè)provides的實(shí)例方法即可。這兩個(gè)成員都是ServiceProvider基類里面定義好的,自定義的時(shí)候,只是覆蓋而已。

在基類中,$defer的默認(rèn)值是false,表示這個(gè)服務(wù)提供者不需要延遲注冊(cè)。provides方法,只要簡(jiǎn)單的返回這個(gè)服務(wù)提供register方法里面,注冊(cè)的所有服務(wù)綁定名稱即可。

延遲注冊(cè)的服務(wù)提供者的機(jī)制是:

  • 當(dāng)laravel初始化服務(wù)提供者的實(shí)例后,如果發(fā)現(xiàn)這個(gè)服務(wù)提供者的$defer屬性為true,那么就不會(huì)去調(diào)用它的register方法

  • 當(dāng)laravel解析一個(gè)服務(wù)的時(shí)候,如果發(fā)現(xiàn)這個(gè)服務(wù)是由一個(gè)延遲服務(wù)提供的(它怎么知道這個(gè)服務(wù)是延遲服務(wù)提供的,是provides方法告訴它的),那么就會(huì)先把這個(gè)延遲服務(wù)提供者先注冊(cè),再去解析。這個(gè)可以看看Illuminate\Foundation\Application的make方法就清楚了: 

    + View code

6)還記得容器實(shí)例結(jié)構(gòu)上幾個(gè)帶有providers名稱的屬性數(shù)組吧:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

在了解以上provider的機(jī)制后,這幾個(gè)數(shù)組的作用也就比較清晰了。其中serviceProviders用來(lái)存放所有已經(jīng)注冊(cè)完畢的服務(wù)提供者:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

loadedProviders跟serviceProviders的作用類似,只是存儲(chǔ)的記錄形式不同:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

deferredProviders用來(lái)存儲(chǔ)所有的延遲注冊(cè)的服務(wù)提供者:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

跟前面兩個(gè)不同的是,deferredProviders存儲(chǔ)的記錄的key值并不是服務(wù)提供者的類型名稱,而是服務(wù)提供者的provides返回?cái)?shù)組里面的名稱。并且如果一個(gè)服務(wù)提供者的provides里面返回了多個(gè)服務(wù)綁定名稱的話,那么deferredProviders里面就會(huì)存多條記錄:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

這樣是方便根據(jù)服務(wù)綁定名稱,找到對(duì)應(yīng)的服務(wù)提供者,并完成注冊(cè)。當(dāng)服務(wù)的解析的時(shí)候,會(huì)先完成延遲類型的服務(wù)提供者的注冊(cè),注冊(cè)完畢,這個(gè)服務(wù)綁定名稱在deferredProviders對(duì)應(yīng)的那條記錄就會(huì)刪除掉。不過(guò)如果一個(gè)服務(wù)提供者provides了多個(gè)服務(wù)綁定名稱,解析其中一個(gè)服務(wù)的時(shí)候,只移除該名稱對(duì)應(yīng)的deferredProviders記錄,而不是所有。

7)服務(wù)提供者還有一個(gè)小問(wèn)題值的注意,由于php是一門基本語(yǔ)言,在處理請(qǐng)求的時(shí)候,都會(huì)從入口文件把所有php都執(zhí)行一遍。為了性能考慮,laravel會(huì)在第一次初始化的時(shí)候,把所有的服務(wù)提供者都緩存到bootstrap/cache/services.php文件里面,所以有時(shí)候當(dāng)你改了一個(gè)服務(wù)提供者的代碼以后,再刷新不一定能看到期望的效果,這有可能就是因?yàn)榫彺嫠?。這時(shí)把services.php刪掉就能看到你要的效果了。

6. 服務(wù)綁定名稱的別名

前面介紹的別名是在config/app.php的aliases配置節(jié)里面定義的,那個(gè)別名的作用僅僅是簡(jiǎn)化類名的時(shí)候,laravel幫你把長(zhǎng)的類型名注冊(cè)成為簡(jiǎn)短的名稱,然后在全局環(huán)境了里面都能使用。laravel還存在另外一個(gè)別名,就是服務(wù)綁定名稱的別名。通過(guò)服務(wù)綁定的別名,在解析服務(wù)的時(shí)候,跟不使用別的效果一致。別名的作用也是為了同時(shí)支持全類型的服務(wù)綁定名稱以及簡(jiǎn)短的服務(wù)綁定名稱考慮的。

1)如何指定和使用服務(wù)綁定名稱的別名

假如有一個(gè)服務(wù)做如下綁定:

+ View code

那么可以通過(guò)容器方法alias方法指定別名:

+ View code

這個(gè)方法的第一個(gè)參數(shù)是服務(wù)綁定名稱,第二個(gè)參數(shù)是別名。這個(gè)方法調(diào)用后,就會(huì)在容器實(shí)例屬性aliases數(shù)組里面存入一條記錄:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn) 
photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

你看剛才舉例中的別名就已經(jīng)添加到這個(gè)數(shù)組里面。這個(gè)數(shù)組里面每條記錄的key值都是別名。但是value有可能是服務(wù)綁定名稱,也有可能是另外一個(gè)別名。這是因?yàn)閯e名是可以遞歸的。

2)別名支持遞歸

也就是說(shuō),可以對(duì)別名再指定別名:

+ View code

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

3)別名如何應(yīng)用于服務(wù)解析

在解析服務(wù)的時(shí)候,會(huì)先確定這個(gè)服務(wù)名稱是否為一個(gè)別名(只要看看在aliases數(shù)組里是否存在記錄即可),如果不是別名,直接用這個(gè)服務(wù)名稱進(jìn)行解析。如果這個(gè)服務(wù)名稱是一個(gè)別名,那么就會(huì)通過(guò)調(diào)用的方式,找到最終的服務(wù)名稱:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

如下所有的服務(wù)解析都是等價(jià)的:

+ View code

4)另外一種指定別名的方式

可以在服務(wù)綁定的時(shí)候,進(jìn)行別名的指定。只要按照如下的方式進(jìn)行綁定即可:

+ View code

也就是把服務(wù)綁定名稱換成數(shù)組形式而已。數(shù)組記錄的key值就是服務(wù)名稱,value值就是別名。

7. 依賴注入的機(jī)制

+ View code

在這個(gè)舉例中,定義了一個(gè)Service類,這個(gè)類有一個(gè)實(shí)例成員$app,它需要一個(gè)實(shí)現(xiàn)了\Illuminate\Contracts\Foundation\Application 接口的實(shí)例對(duì)象,也就是容器實(shí)例。然后通過(guò)直接使用類型名稱的方式把這個(gè)類快速地綁定到了容器。app()->singleton(Service::class),等價(jià)于app()->singleton(Service::class,Service:class)。這種通過(guò)類名形式的綁定,laravel在解析的時(shí)候會(huì)調(diào)用這個(gè)類型的構(gòu)造函數(shù)來(lái)實(shí)例化服務(wù)。并且在調(diào)用構(gòu)造函數(shù)的時(shí)候,會(huì)通過(guò)反射獲得這個(gè)構(gòu)造函數(shù)的參數(shù)類型,然后從容器已有的綁定中,解析出對(duì)應(yīng)參數(shù)類型的服務(wù)實(shí)例,傳入構(gòu)造函數(shù)完成實(shí)例化。這個(gè)過(guò)程就是所謂的依賴注入。

在以上代碼中,完全沒(méi)有手寫(xiě)的new Service(app())代碼,就能正確地解析到service實(shí)例,這就是依賴注入的好處:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

當(dāng)一個(gè)類需要某個(gè)服務(wù)類型的實(shí)例時(shí),不需要自己去創(chuàng)造這個(gè)服務(wù)的實(shí)例,只要告訴容器,它需要的實(shí)例類型即可,然后容器會(huì)根據(jù)這個(gè)類型, 解析出滿足該類型的服務(wù)。如何根據(jù)參數(shù)類型解析出該參數(shù)類型的服務(wù)實(shí)例呢?其實(shí)就是根據(jù)參數(shù)類型的類型名稱進(jìn)行解析得到的,所以依賴注入能夠成功的前提是根據(jù)參數(shù)類型的名稱,能夠成功地解析到一個(gè)服務(wù)對(duì)象。以上之所以能夠通過(guò)Illuminate\Contracts\Foundation\Application 這個(gè)名稱解析到服務(wù),那是因?yàn)樵谌萜鲗?shí)例aliases數(shù)組里面有一條Illuminate\Contracts\Foundation\Application 的別名記錄:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

也就是說(shuō)Illuminate\Contracts\Foundation\Application 實(shí)際上是app這個(gè)服務(wù)綁定名稱的一個(gè)別名,所以laravel在解析Illuminate\Contracts\Foundation\Application的時(shí)候,就能得到對(duì)應(yīng)的服務(wù)實(shí)例了。

這些別名屬于laravel容器核心的別名,在laravel初始化的時(shí)候會(huì)被注冊(cè):

+ View code

依賴注入更多地用在接口編程當(dāng)中,就像上面的舉例類似。再看一個(gè)自定義的例子:

+ View code

按接口進(jìn)行編程,像Service這種業(yè)務(wù)類,只需要聲明自己需要一個(gè)Inter類型的實(shí)例即可。接口的好處在于解耦,將來(lái)要更換一種Inter的實(shí)現(xiàn),不需要改Service的代碼,只需要在實(shí)例化Service的時(shí)候,傳入另外一個(gè)Inter的實(shí)例即可。有了依賴注入以后,也不用改Service實(shí)例化的代碼,只要把Inter這個(gè)服務(wù)類型,重新做一個(gè)綁定,綁定到另外一個(gè)實(shí)現(xiàn)即可。

+ View code

8. 其它

還有兩個(gè)小點(diǎn),也值的介紹一下。

1) 容器實(shí)例的instance方法

這個(gè)方法其實(shí)也是完成綁定的作用,但是它跟前面介紹的三種綁定方法不同,它是把一個(gè)已經(jīng)存在的實(shí)例,綁定到容器:

+ View code

這是它的源碼:

+ View code

從這個(gè)代碼可以看到,instance方法,會(huì)直接把外部實(shí)例化好的對(duì)象,直接存儲(chǔ)到容器的instances里面。如果這個(gè)服務(wù)綁定名稱存在bindings記錄,那么還會(huì)做一下重新綁定的操作。也就是說(shuō),通過(guò)intance方法綁定,是直接綁定服務(wù)實(shí)例,而原來(lái)的bind方法其實(shí)只是綁定了一個(gè)閉包函數(shù),服務(wù)實(shí)例要到解析的時(shí)候才會(huì)創(chuàng)建。

2) 容器實(shí)例的share方法

容器實(shí)例的singleton方法,綁定的服務(wù)在解析的時(shí)候,始終返回第一次解析的對(duì)象。還有一個(gè)方式也能做到這個(gè)效果,那就是使用share方法包裝服務(wù)綁定的匿名函數(shù):

+ View code

當(dāng)我們使用app('cas')解析的時(shí)候,始終拿到的都是第一次解析創(chuàng)建的那個(gè)CasManager對(duì)象。這個(gè)跟share方法的實(shí)現(xiàn)有關(guān)系:

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

從源碼看出,share方法把服務(wù)綁定的閉包再包裝了一下,返回一個(gè)新的閉包,并且在這個(gè)閉包里面,加了一個(gè)靜態(tài)$object變量,它會(huì)存儲(chǔ)原始閉包第一次解析調(diào)用后的結(jié)果,并在后續(xù)解析中直接返回,從而保證這個(gè)服務(wù)的實(shí)例只有一個(gè)。