tag() 與 tagged()。負責彙整的一方不需要知道所有的實作類別。
本頁以 Laravel 13 為對象,並確認了 Framework v13.38.0 的實作與官方測試。綁定的基礎請參閱服務容器,提供者的發布方式請參閱套件開發。
標籤用來歸納服務的註冊鍵
tag() 會將類別名稱等容器註冊鍵加入標籤清單中。單純加上標籤,並不會建立該服務。
tagged() 會回傳一個 iterable,用來列舉註冊在該標籤下的服務。這並不是自動搜尋實作特定介面之類別的功能,而是由各個提供者明確參與的機制。它也與 vendor:publish --tag 用於發布資源的標籤不同。
建立可擴充的報表套件
準備實作共用的契約
舉例來說,我們建立一個彙整多份報表的acme/reports 套件。前提是 Composer 的 PSR-4 設定中,Acme\Reports\ 對應到 src/。
在 src/Contracts/Report.php 中定義外掛要實作的契約。
src/RuntimeReport.php 與 src/MemoryReport.php。
彙整服務接收 iterable
src/ReportCollector.php 會在列舉各個實作時呼叫收集處理。將公開的標籤名稱定義為常數,使用端也能參照相同的名稱。
iterable 型別無法驗證元素的型別,tag() 本身也不會確認是否為 Report 的實作。因此本範例在彙整端也會確認契約。
在 register() 中只註冊綁定與標籤
在src/ReportsServiceProvider.php 中註冊標準實作與彙整服務。彙整服務的工廠會直接傳入取得的 iterable。
Report 的 DailyReport 等類別,並在自己提供者的 register() 中加入相同的標籤。
app(ReportCollector::class)->collect(),新增的實作也會被彙整進來。即使參與的實作增加,也不需要修改 ReportCollector 的建立處理。
tagged() 與 giveTagged() 的建立時機不同
在所確認的實作中,已註冊標籤的tagged() 會回傳 RewindableGenerator。各服務的 make() 會在列舉時才被呼叫,若在中途停止列舉,剩餘的服務就不會被建立。
此外,RewindableGenerator 每次列舉時都會建立新的 Generator。它並不是快取服務結果的功能,若是一般的 bind(),重新列舉時實例也會重新建立。singleton() 或 scoped() 的服務則依照各自的共用規則。
另一方面,contextual binding 的 giveTagged() 會在解析時以 iterator_to_array() 將 tagged() 的結果轉換為陣列。
若想以陣列一次解析所有相依性,可以將前面附有工廠的
bind() 替換為以下的註冊方式。
$reports 的型別是 iterable,以此方式保存的值仍是陣列。單憑參數的型別無法保證延遲解析。此外,giveTagged() 所做的是解析實例,而不是呼叫各實作的 collect()。
Tag 屬性的 resolve() 會直接回傳 tagged()。使用屬性時,請像情境式屬性那樣指定在建構子參數上,並將類別的建構交給容器。屬性並不會自動套用到自訂工廠內的 new。
不破壞外掛的註冊契約
明確處理重複註冊與順序
tag() 是加入清單的處理,即使再次加入相同的註冊鍵,也不會移除重複項目。若同一個實作被列舉兩次,即使 singleton 回傳的是同一個物件,collect() 仍會被呼叫兩次。
請將標籤註冊集中在負責各實作的提供者中的單一位置。bindIf() 是避免覆寫綁定的 API,並不能防止之後 tag() 的重複。
列舉順序就是加入標籤清單的順序。標籤沒有依優先度排序的處理,因此請勿讓不同套件之間的處理順序僅依賴提供者的載入順序。若順序需要作為公開規格,請將優先度或識別碼納入契約,並設計成由彙整端明確排序。
先註冊負責新增標籤的提供者
未註冊標籤的tagged() 不會拋出例外,而是回傳空陣列。之後的標籤註冊,並不會反映到先前取得的空陣列,或已由 giveTagged() 建立的陣列中。
對於已註冊標籤的延遲列舉,也請勿以執行途中變更清單為前提。所確認的 tagged() 會在取得時傳入數量,而在列舉時才參照標籤清單。若設計成在所有提供者註冊完成後才使用,也能避免數量與列舉對象不一致。
延遲提供者的啟動條件可參閱 DeferrableProvider,安裝時的載入則可參閱套件自動探索。
將標籤與服務的生命週期分開
加上標籤並不會讓服務自動成為 singleton 或 scoped。本範例使用一般的bind(),也不會將收集結果儲存在彙整物件中。
特別是若將以 giveTagged() 解析的陣列保存在 singleton 的彙整服務中,陣列內的 scoped 服務也可能一直被保留。重設 scope 並不會替換已儲存在其他物件中的參照。在 Octane 或佇列 worker 中使用的設計,也請參閱套件服務的生命週期設計。
測試延遲解析與陣列化
在套件的 Composer 自動載入與 PHPUnit 環境中,可以在tests/ReportCollectorTest.php 加入以下測試。這裡僅使用容器呼叫提供者的註冊,並驗證建立次數。包含整合到應用程式與自動探索在內的測試,請以 Orchestra Testbench 補足。