Laravel-–-a-beautiful-PHP-framework

讀 source code 研究 Laravel IoC Container 實作的一點心得

前陣子在SO回答了一個問題

http://stackoverflow.com/questions/27341595/contracts-in-laravel-5/29812568

這問題不難,官方文件就有寫。

過幾天,下面的comments接著有網友 amosmos 提問 IoC Container 實作細節的問題。

他的問題考倒我了,完全無法回答。

於是花了幾天讀原始碼,最後終於搞懂了。

跟大家分享一下這個心得,也順便分享下我在過程中,讀原始碼的整套思路。

正文開始

簡單來說,網友 amosmos 的問題如下:

在 Illuminate/Foundation/Application.php 檔,會在 function registerCoreContainerAliases 看到這些:

讓我們先稱呼’app’、’auth’這些字串為 IoC Container 內的 「key」 好了。

乍看之下,大部份的 service 都在這時候被綁定到 IoC Container內,對應到某個 key。

看起來非常合理,也很漂亮,對吧?Laravel會用到的service都在這時候統一登記好、綁進這IoC Container。

問題來了,在 config/app.php 內,有這個陣列:

你如果熟悉 Service Provider (下文以SP代稱) 的話,就會知道每個 SP 內必定有 function register,負責綁定service到container。

以CacheServiceProvider來說,會出現這幾行:

問題來了,在上面的 Application.php,已經有這個了:

所以這CacheManager,到底是何時被綁定到 ‘cache’ 這個key上的?

是Application在建構式呼叫registerCoreContainerAliases時綁定的嗎?

還是在某處讀取 config/app.php 的providers時綁定的?

這邊出現了我們的問題一:

Q1: 難道重複綁定了兩次嗎?CacheManager被初始化了兩次?所以是 Laravel 原始碼的瑕疵?

讓我們做個實驗吧,把Application.php裡面這行comment掉:

接著在cmd輸入

php artisan tinker

很好,跑起來了,至少laravel沒有炸掉。

接著試試cache功能是否還活著?

看起來沒問題!難道真的是Laravel的瑕疵?

接著把 Application.php恢復原狀,然後把CacheServiceProvider的綁定給comment掉:

接著在cmd輸入

php artisan tinker

[ReflectionException]
Class cache does not exist

Laravel炸掉了!根本不能跑!

所以不算是重複綁兩次?

那就出現了我們的問題二:

Q2: 為什麼 Application.php 內的key給comment掉,功能還是正常運作?

反覆翻閱一下 Application.php、Illuminate\Container\Container.php,會發現 function registerCoreContainerAliases 似乎將整串陣列存在container的成員變數$aliases上?

接著尋找讀取config\app.php的地方,會找到 Illuminate\Foundation\Bootstrap\LoadConfiguration.php這檔案,然後發現Illuminate\Foundation\Bootstrap\RegisterProviders.php這檔案,翻閱Application.php和 Illuminate\Foundation\ProviderRepository.php之後,會發現config\app.php內的providers似乎會存在container的成員變數$bindings上?

有一個很簡單的方法去驗證這件事情。

將 Illuminate\Container\Container.php 的 $aliases 與 $bindings 成員變數改成 public,接著 php artisan tinker

會發現的確是這樣沒錯!

事到如今,我們會發現Q1的問題解決了:Application.php是存在aliases內,而config\app.php是存在bindings內。並沒有重複綁定兩次的問題!而是container實作細節沒搞清楚的問題!

所以真正的問題應該是這個:

Q3: IoC Container內的$aliases跟$bindings分別是什麼?兩者如何互動?

要解決這個問題,必須去讀container最核心的function make部份程式碼。

看到這邊,我們幾乎可以確定$aliases跟$bindings的關係為何了:

我們在使用 App::make($abstract) 時,輸入的abstract,實際上主要是去bindings找對應並叫出來。

而aliases只是紀錄abstract的其他別名而已,舉例如下:

還可以再做進一步的測試,將Container的function alias改成public,然後做以下測試:

所以正式回答Q3的問題:

Service Provider的function register內會去登記到IoC Container的abstract,實際上是存在$bindings或$instances陣列。

而App::make($abstract)實際上也是從這兩個陣列找東西出來用。

$aliases陣列只是存abstract的其他別名,讓 Binding Interfaces To Implementations 成為可能,也允許直接打類別全名進去。

結論

讀Laravel原始碼有幾個小技巧

1. 多用php artisan tinker去跟laravel互動

2. 把一些source code的變數、函數改成public,在tinker硬倒出來觀察

3. 有些東西很難在一天內搞清楚,睡個覺醒來再看通常很有幫助

(完)