v13.36.0 的容器、佇列 worker 以及 Octane 的實作,把服務容器的基本註冊方式與套件的長期維護連結起來。僅僅選擇 scoped(),並不會重設物件之間的參照或靜態狀態。
依服務的保存期間選擇註冊方式
此表比較的是一般的解析流程。明確指定參數的解析或 contextual binding 等特殊路徑,請另行確認。
即使使用
bind(),若將取得的服務存放到其他 singleton 的屬性中,該參照仍會留存。註冊方式是容器的建立與重複使用規則,並不是強制重新建立使用端所保存物件的規則。
scoped 結合了共用註冊與移除對象的記錄
所確認的Container::scoped() 會執行以下兩件事。
- 將註冊鍵記錄到
scopedInstances。 - 呼叫
singleton(),註冊為共用綁定。
instances 中。forgetScopedInstances() 會針對已記錄的鍵,對已儲存的實例執行 unset()。綁定的建立處理仍會保留,因此下一次一般解析時可以建立新的物件。
此處理不會初始化舊物件的屬性,也不會改寫其他物件所持有的參照。仍被參照的舊物件,依然可以繼續被使用。
建立邊界的是執行環境
容器本身並不會監視 HTTP 請求或 Job 的開始。- 佇列 worker:
QueueServiceProvider會將重設 scope 的回呼傳給 Worker。Worker::daemon()會在取得下一個 Job 之前的迴圈中呼叫它,並在回呼內執行forgetScopedInstances()以及清除 Facade 已解析的實例等處理。 - Octane:官方的預設設定會將
FlushTemporaryContainerInstances註冊到OperationTerminated。此監聽器會呼叫forgetScopedInstances()。 - 自訂的常駐指令或迴圈:單純重複執行
make()並不會形成邊界。必須由執行基礎設施端設計生命週期。
將處理內共用的狀態設為 scoped
以配送套件為例,假設它想在單一請求或 Job 內共用配送目的地的識別碼。不直接保存 HTTP 的 Request,只儲存必要的值。handle() 中,設定已完成輸入驗證與權限確認的識別碼。在同一個生命週期中解析此型別的其他服務,可以參照相同的 context。
scoped() 也不會持久化到資料庫或外部快取。要傳給下一個 Job 的值,請明確地作為 Job 的資料傳遞。
在 Octane 中,服務提供者的
register() 與 boot() 會在 worker 初次啟動時執行。請勿設計成在 boot() 中將目前使用者或配送目的地存入 context。每次處理的值,應在處理開始後再設定。從 singleton 到 scoped 的參照不會自動更新
以下的註冊方式,即使只把 context 設為 scoped,也無法達到預期效果。DeliveryService 在建構子中將 context 存入屬性,就會一直持有最初建立的 context。即使從容器移除已儲存的 context,已建立的 DeliveryService 的屬性也不會改變。
請依服務的職責選擇對策。
- 以方法參數傳遞每次處理所需的值。若不需要共用狀態,這是最簡單的方式。
- 將持有端也設為 scoped。讓
DeliveryService與 context 兩者都依處理單位重新建立。 - 傳入在使用時才解析的 resolver。若必須維持 singleton,就每次從目前的容器取得所需的服務。
DeliveryService 必須改為在建構子中接收 Closure,並成為每次操作時都呼叫它的 API。若在建構子內只呼叫一次 resolver 並保存結果,就無法解決問題。這是將官方 Octane 文件中的 container resolver 應用到套件專屬 context 的設計範例。
也要確認 scoped 不會清除的狀態
scoped() 不會自動移除以下狀態。
- 新增到套件自有靜態屬性中的值
- 存放在其他 singleton 或全域變數中的 context 參照
- 保存在 singleton 內部的「目前使用者」「目前配送目的地」
- 自訂 Facade 或快取層所持有的參照
撰寫在同一個容器中跨越邊界的測試
若 HTTP 測試只是每次建立新的應用程式,可能會遺漏常駐程序持續使用同一個容器時的問題。首先以容器單體驗證同一處理內的共用,以及跨越邊界後的重新建立。 以下測試採用 PHPUnit 格式。第一個測試與官方的 Container 測試一樣使用forgetScopedInstances(),在不啟動整個 Worker 的情況下確認邊界的作用。
容器單體測試並不會驗證 Octane 的 sandbox 或佇列 worker 的實際執行路徑。在聲明支援的執行環境中,也需要進行連續處理的整合測試。在變更註冊方式或服務實作的部署中,請重新載入常駐程序以套用新的程式碼。
相關頁面
套件的服務解析後掛鉤
確認 afterResolving 與共用實例的取得,以及重新解析時設定的差異。
Laravel Octane
確認 Request 與容器的保存,以及常駐程序在維運上的注意事項。
參考的一手資料
- Laravel 官方文件:Binding scoped singletons
- Laravel 官方文件:Dependency injection and Octane
- Container:scoped()、singleton()、forgetScopedInstances()、resolve()
- ContainerTest:scoped 的共用與重新建立
- QueueServiceProvider:傳給 Worker 的 resetScope
- Worker:在 daemon() 中重設 scope
- Octane:FlushTemporaryContainerInstances
- Octane:OperationTerminated 的預設監聽器