老司機帶你深入 Laravel 之 ServiceProvider 原理

Dennis_Ritchie發表於2019-12-09

生活的感慨

生活中的朋友不需要太多,有那麼幾個就可以了,去關心那些真心關心你的人,至於其他人嘛,都是路人,不要把那些真心關心你的人,當成一坨屎,他們都是你最為寶貴的財富。溫文爾雅,不卑不亢,無論是遇到漂亮的姑娘還是公司的領導,都要波瀾不驚,自然優雅。

博文簡介

在今天的這一篇博文中,我會帶領大家深入的學習ServiceProvider,為什麼要講這個,我們來看官方的文件結構。

老司機帶你深入Laravel之ServiceProvider

Laravel官方在架構理念這裡把它列出來,所以ServiceProvider的重要性,就不言而喻了,但是,今天在這裡我不打算講Laravel的基礎用法,如果你不清楚這塊的內容,你可以參考官方文件,Laravel之ServiceProvider,我今天要講的是,Laravel是如何引導和載入ServiceProvider的。

閱讀基礎

在閱讀這篇博文之前,希望你已經知道如何使用ServiceProvider,這個我在上面講過了。

一定要閱讀我的上一篇博文,老司機帶你深入理解 Laravel 之 Facade,這不是在開玩笑,我不想再講我已經講過的東西,希望你對自己負責,不然的話,我覺得你沒有必要再閱讀下面的內容了,浪費自己的時間,有這個時間還不如干點兒正事。

深入原始碼

在類Illuminate\Foundation\Http\Kernel類的bootstrappers屬性中,如下:
老司機帶你深入Laravel之ServiceProvider

我們只關心\Illuminate\Foundation\Bootstrap\RegisterProviders::class這個類,在這裡Laravel會載入所有的ServiceProvider,下面我們進入到它的bootstrap方法中:

老司機帶你深入Laravel之ServiceProvider

可以看到它的bootstrap方法很簡單,這裡直接呼叫了Application類的registerConfiguredProviders方法,這個方法如下所示:

老司機帶你深入Laravel之ServiceProvider

上面的程式碼你看不懂沒關係,我仔細給你解釋下,在這之前,我提醒大家,去看看Collection類的使用,估計很多人沒使用過,真的很好用,$this->config['app.providers']就是你在config/app.php的providers屬性中所定義的所有的ServiceProvider,Collection的partition方法會使用它的回撥引數,重新生成一個只有2個元素的Collection物件,2個子元素都是Collection類的物件,第一個子元素包含的都是Laravel自身定義的ServiceProvider,第二個Collection包含了我們自己定義的ServiceProvider和第三方包定義的,這裡的PackageManifest::class的providers方法會返回Composer包定義的,至於為啥是這樣,建議你去看我在老司機帶你深入理解 Laravel 之 Facade中的說明,不再陳述。Collection類的splice方法將其它包定義的provider加入到當前容器中。Application類的getCachedServicesPath方法返回快取的路徑,這個路徑指的是啥呢?就是下面這個:

老司機帶你深入Laravel之ServiceProvider

這是一個快取檔案,記錄了當前系統所有的ServiceProvider,你一定要去看一下這個檔案,檔案的結構一目瞭然。
ProviderRepository這個類很簡單,我們的ServiceProvider就是在這個類中進行載入的,下面我們來看它的load方法,它的引數就是系統所有的ServiceProvider。

老司機帶你深入Laravel之ServiceProvider

首先呼叫loadManifest方法,這個方法很簡單,它的作用就是載入專案根目錄下面的bootstrap\cache\packages.php檔案,就是我上面的截圖,上面說了這是一個快取檔案,回到load方法中,繼續呼叫shouldRecompile方法,這個方法會比較當前所有的ServiceProvider和之前快取的ServiceProvider是否是相等的,如果相等,也就是說我們可以忽略編譯的過程,這裡我們假設快取失效或者是不存在,那麼compileManifest方法就會被呼叫,開始編譯所有的ServiceProvider,下面是這個方法的程式碼:

老司機帶你深入Laravel之ServiceProvider

首先呼叫freshManifest方法,freshManifest方法很簡單,如下:

老司機帶你深入Laravel之ServiceProvider

緊接著遍歷所有的ServiceProvider,createProvider會建立對應ServiceProvider類的物件,在這裡我們要提一句,就是Laravel中,所有的ServiceProvider都繼承自Illuminate\Support\ServiceProvider類。Illuminate\Support\ServiceProvider類有一個方法isDeferred,表示當前的ServiceProvider是延遲的還是非延遲的,如果ServiceProvider的話,它的register方法不會馬上被呼叫,相反,此時的ServiceProvider應該提供provides方法,我們開啟bootstrap/cache/services.php檔案可以看到,Laravel中存在很多的ServiceProvider是延遲的,截圖如下:

老司機帶你深入Laravel之ServiceProvider

開啟Illuminate\Cache\CacheServiceProvider檔案,它的provides方法如下:

老司機帶你深入Laravel之ServiceProvider

所以如果以後,你從容器裡面獲取cache,cache.store或者是memcached.connector的時候,都會導致CacheServiceProvider的register方法被呼叫,這也就是延遲ServiceProvider的由來。回到compileManifest方法中,它會遍歷provides方法的返回結果,並且把它儲存到$manifest陣列中,$manifest陣列的格式和freshManifest方法返回的是一樣的,上面的截圖已經說明一切。$manifest['when']記錄事件,這個我們很少用到,暫不分析它,繼續往下走
,如果ServiceProvider不是延遲的,那麼把它加入到$manifest['eager']陣列中,最後呼叫writeManifest方法把$manifest寫入到bootstrap/cache/services.php快取檔案中。好了,compileManifest方法分析完了,我們回到load方法中:

老司機帶你深入Laravel之ServiceProvider

$manifest['eager']中儲存的provider是需要立即被註冊的,這裡的Application類的register方法被呼叫,這個方法是專門用於註冊ServiceProvider的,這個函式我已經在老司機帶你深入理解 Laravel 之 Facade講過了,大家不記得可以回去看一下,主要就是ServiceProvider的register方法被呼叫。回到load方法中,Application的addDeferredServices方法被呼叫,該方法很簡單:

老司機帶你深入Laravel之ServiceProvider

到這裡為止,ServiceProvider的載入基本講完了,但是你是否還有疑惑,就是延遲的ServiceProvider是什麼時候載入的,下面我們來看一下,Application重寫了Container的make方法:

老司機帶你深入Laravel之ServiceProvider

這裡首先判斷需要載入的服務是不是屬於延遲ServiceProvider的,如果是的話,loadDeferredProvider方法被呼叫,如下:

老司機帶你深入Laravel之ServiceProvider

這個放呼叫到了registerDeferredProvider方法,如下:

老司機帶你深入Laravel之ServiceProvider

這段程式碼是不是很熟悉,我就不再解釋了。

如果你自己寫過ServiceProvider的話,那麼你可能會實現boot方法,這個方法是啥時候被呼叫的呢?還記得這張圖麼?
老司機帶你深入Laravel之ServiceProvider

這裡的BootProviders就是這裡的關鍵,它的bootstrap方法如下:

老司機帶你深入Laravel之ServiceProvider

Application的boot方法,如下:

老司機帶你深入Laravel之ServiceProvider

boot方法呼叫bootProvide方法,如下:

老司機帶你深入Laravel之ServiceProvider

這裡檢查如果你的ServiceProvider實現了boot方法,那麼就會呼叫,到這裡位置Laravel的ServiceProvider就講解完畢了,如果你存在疑惑或者是需要幫助,可以加這個群:

總結

沒有人天生就能夠讀懂複雜的程式碼,你需要的是耐心和對成功的渴望,不要給自己退縮的理由,你真的可以,我覺得你也應該這麼做。

如果有不懂的地方,可以加我的qq:1174332406,或者是微信:itshardjs,公眾號:LearnCodeHard

相關文章