跳轉到主要內容
若要讓套件不只能用於一般的 HTTP 請求,也能用於 Octane 或佇列 worker,就必須決定服務可以在多長期間內保存哪些內容。若將每個請求的資料存放在 singleton 中,其他處理也可能參照到舊的資料。 本頁將確認 Laravel Framework v13.36.0 的容器、佇列 worker 以及 Octane 的實作,把服務容器的基本註冊方式與套件的長期維護連結起來。僅僅選擇 scoped(),並不會重設物件之間的參照或靜態狀態。

依服務的保存期間選擇註冊方式

此表比較的是一般的解析流程。明確指定參數的解析或 contextual binding 等特殊路徑,請另行確認。 即使使用 bind(),若將取得的服務存放到其他 singleton 的屬性中,該參照仍會留存。註冊方式是容器的建立與重複使用規則,並不是強制重新建立使用端所保存物件的規則。

scoped 結合了共用註冊與移除對象的記錄

所確認的 Container::scoped() 會執行以下兩件事。
  1. 將註冊鍵記錄到 scopedInstances。
  2. 呼叫 singleton(),註冊為共用綁定。
實例本身與 singleton 一樣儲存在 instances 中。forgetScopedInstances() 會針對已記錄的鍵,對已儲存的實例執行 unset()。綁定的建立處理仍會保留,因此下一次一般解析時可以建立新的物件。 此處理不會初始化舊物件的屬性,也不會改寫其他物件所持有的參照。仍被參照的舊物件,依然可以繼續被使用。

建立邊界的是執行環境

容器本身並不會監視 HTTP 請求或 Job 的開始。
  • 佇列 worker:QueueServiceProvider 會將重設 scope 的回呼傳給 Worker。Worker::daemon() 會在取得下一個 Job 之前的迴圈中呼叫它,並在回呼內執行 forgetScopedInstances() 以及清除 Facade 已解析的實例等處理。
  • Octane:官方的預設設定會將 FlushTemporaryContainerInstances 註冊到 OperationTerminated。此監聽器會呼叫 forgetScopedInstances()。
  • 自訂的常駐指令或迴圈:單純重複執行 make() 並不會形成邊界。必須由執行基礎設施端設計生命週期。
請勿在一般的應用程式處理或套件的 boot() 中,自行判斷呼叫 forgetScopedInstances()。這會影響整個容器的 scoped 服務。此外,包含同步執行在內的所有 Job 路徑,不一定都會經過與常駐 Worker 相同的重設路徑。

將處理內共用的狀態設為 scoped

以配送套件為例,假設它想在單一請求或 Job 內共用配送目的地的識別碼。不直接保存 HTTP 的 Request,只儲存必要的值。
在套件的服務提供者中,建立時不讀取使用端的狀態,而是註冊一個空的 context。
在使用端應用程式的控制器或 Job 的 handle() 中,設定已完成輸入驗證與權限確認的識別碼。在同一個生命週期中解析此型別的其他服務,可以參照相同的 context。
此 context 並不是授權功能。能夠設定識別碼,與是否被允許存取該配送目的地是兩回事。此外,scoped() 也不會持久化到資料庫或外部快取。要傳給下一個 Job 的值,請明確地作為 Job 的資料傳遞。
在 Octane 中,服務提供者的 register() 與 boot() 會在 worker 初次啟動時執行。請勿設計成在 boot() 中將目前使用者或配送目的地存入 context。每次處理的值,應在處理開始後再設定。

從 singleton 到 scoped 的參照不會自動更新

以下的註冊方式,即使只把 context 設為 scoped,也無法達到預期效果。
若 DeliveryService 在建構子中將 context 存入屬性,就會一直持有最初建立的 context。即使從容器移除已儲存的 context,已建立的 DeliveryService 的屬性也不會改變。 請依服務的職責選擇對策。
  1. 以方法參數傳遞每次處理所需的值。若不需要共用狀態,這是最簡單的方式。
  2. 將持有端也設為 scoped。讓 DeliveryService 與 context 兩者都依處理單位重新建立。
  3. 傳入在使用時才解析的 resolver。若必須維持 singleton,就每次從目前的容器取得所需的服務。
在第 3 種方式的範例中,不是將舊的 context 擷取到閉包中,而是在呼叫時才解析。
此範例中的 DeliveryService 必須改為在建構子中接收 Closure,並成為每次操作時都呼叫它的 API。若在建構子內只呼叫一次 resolver 並保存結果,就無法解決問題。這是將官方 Octane 文件中的 container resolver 應用到套件專屬 context 的設計範例。

也要確認 scoped 不會清除的狀態

scoped() 不會自動移除以下狀態。
  • 新增到套件自有靜態屬性中的值
  • 存放在其他 singleton 或全域變數中的 context 參照
  • 保存在 singleton 內部的「目前使用者」「目前配送目的地」
  • 自訂 Facade 或快取層所持有的參照
請區分標準執行基礎設施所做的重設,與套件自有狀態的重設。減少保存每次處理狀態的位置,會比在所有執行環境中維護自訂的清除處理更簡單。

撰寫在同一個容器中跨越邊界的測試

若 HTTP 測試只是每次建立新的應用程式,可能會遺漏常駐程序持續使用同一個容器時的問題。首先以容器單體驗證同一處理內的共用,以及跨越邊界後的重新建立。 以下測試採用 PHPUnit 格式。第一個測試與官方的 Container 測試一樣使用 forgetScopedInstances(),在不啟動整個 Worker 的情況下確認邊界的作用。
第二個測試記錄了參照會留存的行為。實際的套件中,也請加入驗證所採用的對策確實會使用新的 context 或值的測試。 容器單體測試並不會驗證 Octane 的 sandbox 或佇列 worker 的實際執行路徑。在聲明支援的執行環境中,也需要進行連續處理的整合測試。在變更註冊方式或服務實作的部署中,請重新載入常駐程序以套用新的程式碼。

相關頁面

套件的服務解析後掛鉤

確認 afterResolving 與共用實例的取得,以及重新解析時設定的差異。

Laravel Octane

確認 Request 與容器的保存,以及常駐程序在維運上的注意事項。

參考的一手資料

最後修改於 2026年10月14日