v13.39.0 的官方原始碼。基本操作請參閱 Session,提供者的自動載入請參閱套件自動探索。
extend 接收的是處理器而非 Store
Session::extend() 的回呼會回傳實作 PHP SessionHandlerInterface 的儲存用處理器。它並不是直接回傳實作 Illuminate\Contracts\Session\Session 的 Store 或快取 Repository 的 API。
SessionManager::callCustomCreator() 會將回呼的回傳值傳給 buildSession(),並依 session.encrypt 以 Store 或 EncryptedStore 包裝。此時也會套用 Cookie 名稱與 session.serialization。
在一般的 HTTP 處理中,StartSession 中介層負責管理 Session 的啟動與儲存。為了提供處理器,套件端不需要呼叫 session_start() 或 session_set_save_handler()。
分開思考儲存契約
實作自訂後端時,需要滿足以下職責。
由於
Store 負責序列化與還原,處理器中不要再對 Session 陣列執行 serialize() 或 json_encode()。啟用加密時,也請直接儲存經 EncryptedStore 處理後的字串。
在 boot 中只註冊建立處理
以下是重複使用內建CacheBasedSessionHandler 的最小範例。它並非實作新的儲存協定,而是示範如何註冊使用具名快取儲存的自訂驅動。假設 Acme\SessionBridge\ 透過 Composer 的 PSR-4 對應到 src/。
在 src/SessionBridgeServiceProvider.php 準備提供者。
Session::extend() 會取得 SessionManager 並註冊建立處理,但此時不會解析回呼內的快取儲存。儲存位置要到該驅動首次被選用時才會建立。請不要在提供者的 boot() 中執行連線確認或資料儲存。
使用自訂的 SessionHandlerInterface 實作時,請替換回呼所回傳的處理器。如果連 new Store(...) 都在回呼中完成,就會與 SessionManager 預期的回傳值不符。
- 提供者以一般的非延遲提供者載入。
- 在 Session 首次被使用前完成註冊。若其他提供者會先解析自訂驅動,也要確認載入順序。
- 驅動名稱請使用套件專屬的名稱。同名的
extend()會覆寫建立處理;若註冊內建驅動的名稱,則會比內建實作更優先。
由使用端應用程式明確選擇
使用者修改既有config/session.php 中對應的鍵,其他設定保持不變。
config/cache.php 既有的 stores 陣列中新增具名儲存。以下是使用 Redis 連線 default 的範例。Redis 的連線設定與所需的用戶端由應用程式端準備。
createRedisDriver(),即使新增 session.connection 或 session.prefix,此回呼也不會自動套用。
此回呼不會變更 Repository 或儲存位置的設定,而是直接使用具名儲存。測試用的 array 儲存在同一個應用程式內也會共用相同的儲存區域。
套件不應無條件改寫 session.driver、Cookie 名稱或應用程式整體的快取前綴。是否採用不與其他用途共用儲存區域的設計,以及能否讓多台伺服器使用同一個儲存位置,也請與使用者確認。
維持 TTL 的單位與儲存格式
CacheBasedSessionHandler 的建構子以分鐘為單位接收保存期間。write() 每次都會將 $minutes * 60 傳給快取 Repository 的 put(),因此在上面的範例中,每次寫入都會更新 TTL。gc() 回傳 0,將失效處理交給快取端。
另一方面,StartSession 傳給處理器 gc($lifetime) 的期間以秒為單位。請避免將 session.lifetime 的值直接當成秒來儲存。
此外,以下變更不只是修改設定名稱就能解決。
若在套件更新中變更這些項目,請考量重新驗證的提示、是否需要遷移處理,以及新舊版本並存的部署。與套件的版本相容性管理相同,儲存格式與設定鍵也應納入需要維護的公開規格。
已建立的 Store 不會只因設定變更而被替換
SessionManager 的父類別Manager 會依名稱保留已建立的驅動。此外,SessionServiceProvider 會將 session.store 註冊為 singleton。
forgetDrivers() 也不是刪除 Session 資料的 API。在執行中的請求裡重新建立 Store,會造成與已讀取的資料或其他參照之間不一致。
此範例的 CacheManager 也會保留具名儲存。若變更了其連線設定,只重新解析 SessionManager 並不足夠。
在使用設定快取的部署中,請重建快取,並重新啟動長時間執行的 worker,讓它載入新的程式碼與設定。應用程式的重新啟動與已儲存資料的遷移是不同的工作。
Session 儲存與請求鎖是兩回事
僅實作自訂處理器,並不會讓針對同一 Session 的並行請求序列化。StartSession 的阻擋功能使用以 session.block_store 選擇的快取儲存的鎖,它不一定與 Session 本體的儲存位置相同。
以 Route::post(...)->block() 等方式控制時,請另外驗證支援鎖的快取儲存、多台伺服器之間的共用,以及等待時間與持有時間。套件不應擅自啟用應用程式整體的阻擋設定。
測試到儲存與重新載入
在使用 Orchestra Testbench 的套件中,請準備載入提供者並實際選用自訂驅動的測試。以下是tests/SessionDriverTest.php 的範例。測試用的 array 快取不使用外部連線。
array 當作正式環境的共用儲存位置。
在公開自訂處理器之前,也請加入以下項目。
- 未儲存或已過期的 ID 會回傳空字串
- 儲存的字串能原樣還原,刪除後無法再取得
- TTL 的單位與更新,以及真實後端中的失效
- 讀取、寫入、刪除失敗時,會得到既定的回傳值或例外
- 啟用
session.encrypt時的往返,以及支援的序列化格式 - 僅載入提供者不會存取儲存位置,也不會搶走內建驅動的選擇
- 視需要加入經過真實
web中介層與 Cookie 的 HTTP 測試,以及並行請求的鎖
相關頁面
Session
確認 Session 的讀寫、快閃資料與 ID 的重新產生。
Manager 的內部實作
確認自訂驅動的建立與保留,以及對預設驅動的委派。
套件的自訂快取儲存
確認作為儲存位置的快取 Store 與 Repository 的分工,以及 TTL 契約。
套件服務的生命週期
確認長時間執行的 worker 中所保留的相依性與狀態隔離。
第一手資料
- 官方 HTTP Session 文件:自訂驅動與阻擋
- 官方套件開發文件
- SessionManager:自訂建立處理與 Store 的建構
- Store:讀取、序列化、儲存、ID 的重新產生
- CacheBasedSessionHandler:TTL 的轉換與儲存契約
- CacheManager:具名儲存與 Redis 設定
- Manager:extend、driver、forgetDrivers
- SessionServiceProvider:Manager 與 session.store 的 singleton 註冊
- StartSession:儲存處理、GC、以快取實現的鎖
- 官方 CacheBasedSessionHandlerTest:未儲存、TTL、刪除的驗證