什麼是 Service Container
Laravel 的 service container 是管理類別依賴並進行依賴注入的機制。所謂依賴注入,是指透過建構子(或有時透過 setter 方法)將類別所需的依賴「注入」到類別中。 看以下例子:PodcastController 需要從 Apple Music 等資料來源取得 Podcast。因此我們注入能取得 Podcast 的服務。透過注入服務,測試時能輕易換成 AppleMusic 服務的 mock(假實作)。
深入理解 service container 對於建構大型 Laravel 應用不可或缺,也對貢獻 Laravel 核心有所幫助。
零設定解析
若類別只依賴其他具體類別(非介面),無需告訴 container 如何解析。例如在routes/web.php 寫下:
此例在路由檔中定義類別僅為示範。實際應用中,service 類別應定義於
app/Services 目錄。Service 類別並注入到路由 handler。無需設定檔即可享受依賴注入。
controller、event listener、middleware 等 Laravel 應用中撰寫的多數類別,其依賴都會透過 container 自動注入。
Binding
基本 binding
多數 binding 會在service provider中登記。在 service provider 內可透過$this->app 屬性存取 container。
bind
用bind 方法傳入類別或介面名稱與 closure 來登記 binding。
App facade。
不依賴介面的類別不需要 bind 到 container。container 會以 reflection 自動解析這些物件。
singleton
singleton 方法會將類別或介面 bind 為只解析一次。一旦解析出的 singleton 之後每次向 container 呼叫都會回傳同一個實例。
singletonIf 方法可以只在該型別尚未登記 binding 時才登記 singleton binding。
Singleton 屬性
也可以在類別或介面加上#[Singleton] 屬性,指示 container 只解析一次。
Scoped singleton binding
scoped 方法將類別或介面 bind 為在 Laravel 請求/job 生命週期內只解析一次。與 singleton 類似,但以 scoped 登記的實例會在 Laravel 應用開始新的「生命週期」時被銷毀,例如 Laravel Octane worker 處理新請求,或 queue worker 處理新 job 時。
scopedIf 方法可以只在該型別尚未登記 binding 時才登記 scoped binding。
Scoped 屬性
也可以在類別或介面加上#[Scoped] 屬性,指示 container 在請求/job 生命週期內只解析一次。
instance
也可用instance 方法將既有物件實例 bind 到 container。之後每次呼叫 container 都會回傳該實例。
將介面 bind 到實作
service container 強大功能之一是可以將介面 bind 到特定實作。例如有EventPusher 介面與 RedisEventPusher 實作:
EventPusher 實作的類別注入 RedisEventPusher。之後只需在建構子 type-hint EventPusher 介面即可。
Bind 屬性
Laravel 還提供更方便的Bind 屬性。在介面加上此屬性,就可以告訴 Laravel 當該介面被要求時要自動注入哪個實作。使用 Bind 屬性時,service provider 中無需再登記。
還可以在介面放多個 Bind 屬性,依環境注入不同實作。
BindWhen 屬性。closure 會收到 container,套用該 binding 時回傳 true。Bind 與 BindWhen 屬性會依宣告順序評估。
使用
BindWhen 屬性需要 PHP 8.5 以上。情境式 binding
有時你會希望對兩個使用相同介面的類別,各自注入不同的實作。例如兩個 controller 分別依賴Illuminate\Contracts\Filesystem\Filesystem contract 的不同實作。Laravel 提供了簡潔流暢的介面來定義這種行為。
when() 可接受單一類別或類別陣列。以 needs() 指定目標介面、以 give() 指定用於解析的 closure。如此一來,即便同樣型別提示 Filesystem 介面,也能依 controller 注入不同的 disk。
情境式屬性
情境式 binding 常用於注入 driver 或設定值的實作,因此 Laravel 提供了各式情境式 binding 屬性,讓你不必在 service provider 中手動定義情境式 binding。 例如可以使用Storage 屬性注入特定的儲存 disk。
Storage 屬性,Laravel 還提供 Auth、Cache、Config、Context、DB、Give、Log、RequestAttribute、RouteParameter、Tag 屬性。
RouteParameter 屬性會解析名稱與屬性名稱相符的路由參數。必要時也可以像 #[RouteParameter('photo')] 這樣明確指定路由參數名稱。
RequestAttribute 屬性會解析目前 Request 實例的輸入值。若想參照與屬性名稱不同的 key,請像 #[RequestAttribute('user_organization')] 這樣明確指定。
Give 屬性用於將介面解析為具體類別時,若想指定特定實作而非透過一般型別提示的自動解析,即可使用。自動解析(以 type-hint 進行 DI)
service container 在解析 controller、event listener、middleware 等類別時,會查看建構子的 type-hint 自動注入依賴。UserRepository 不依賴介面,就不用登記到 container。只要存取路由,container 就會自動解析依賴並注入到 controller。
從 Container 解析
make 方法
用make 方法可從 container 解析類別實例。
makeWith 方法傳入額外引數。
自動注入
實際上,很少直接呼叫make。只要在 container 會解析的類別(controller、event listener、middleware 等)建構子加上 type-hint,container 就會自動注入。
Facade 與 Container 的關係
Laravel 的 facade 為 container 內的物件提供靜態介面。例如Cache::get() 內部會從 container 取得 Cache 服務再呼叫。
建構子注入的實務範例
看實際應用中的典型模式:1
定義介面
2
建立實作類別
3
在 Service Provider bind
4
在 controller 接收注入
Stripe 換成其他供應商,也只需改一處 binding。
後續步驟
Service Provider
學習使用 service provider 登記 binding 的方式。