Skip to main content
若要讓套件不只能用於一般的 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日