跳轉到主要內容
想從套件提供自訂的快取後端時,請將實作儲存處理的 Store 與提供 Laravel API 的 Repository 分開。註冊到 Cache::extend() 的是建立方式,Laravel 並不會自動將回傳的 Store 包裝成 Repository。 本頁確認了 Laravel Framework v13.39.0 的實作。基本操作請參閱快取,提供者的準備請參閱套件開發。

區分驅動名稱與儲存名稱

若設定的 driver 有對應的自訂建立處理,CacheManager::build() 會比內建驅動更優先呼叫它。請不要重複使用 redis 或 array 等名稱,而是使用套件專屬的名稱。

在其他提供者的 boot 之前註冊

若其他套件會在 boot() 中使用快取,而你在自己的 boot() 中註冊驅動,依順序不同可能會來不及。請依照官方文件,在 register() 中註冊應用程式的 booting 回呼。 以下是為了在不依賴外部服務的情況下確認註冊路徑,而使用標準 ArrayStore 的最小範例。請放在 Acme\Memo 套件的 src/MemoServiceProvider.php。
提供者可透過套件自動探索等方式載入。這個只負責註冊擴充的提供者不要設為延遲提供者,讓它在 HTTP 與 Artisan 兩種情境下都會被載入。
  • 在一般啟動流程中,應用程式的 booting 會在所有提供者的 register() 完成之後、各個 boot() 之前執行。這與提供者本身的 $this->booting() 不同。
  • extend() 的回呼會收到應用程式與儲存設定陣列。Store 的建立與外部連線請放在這個建立用回呼中。
  • 這個註冊時機並不保證在其他提供者的 register() 中使用快取也能運作。請將服務的註冊與實際使用分開。
若只需要標準的記憶體內快取,使用 array 驅動就足夠了。這個範例是用來說明註冊與契約,無法用於跨行程的共享或持久化。實作自訂後端時,請將 ArrayStore 的建立部分替換為實作 Illuminate\Contracts\Cache\Store 的類別。
CacheManager::extend() 會將匿名閉包的作用域重新綁定到 Manager。請不要以為非 static 閉包中的 $this 是提供者,而是像範例一樣使用 static 閉包與引數 $app。

在使用端應用程式中新增儲存

在 config/cache.php 既有的 stores 陣列中加入下列內容。請不要將套件設計成會改寫使用端的 default 或整個 stores。
若想設為預設值,在 CACHE_STORE 中指定的也是**catalog 這個儲存名稱**,而不是驅動名稱。為同一個驅動建立多組設定時,每個儲存名稱都會建立各自的 Repository。

自行組裝 Repository

CacheManager::callCustomCreator() 會直接回傳回呼的回傳值。若只回傳 Store,即使 get() 與 put() 能運作,也無法使用 Repository 的 remember() 等方法,TTL 的解讀方式也會改變。 範例中的 repository($store, $config) 負責下列事項。
  • 以 Illuminate\Cache\Repository 包裝 Store。
  • 將 Manager 加入設定的 store 名稱傳給 Repository。
  • 若已註冊事件 Dispatcher,且 events 設定未停用,就將 Dispatcher 設定給 Repository。
這並不是將所有自訂設定套用到 Store 的處理。連線目標、prefix、序列化等,需由 Store 的實作與建立處理自行反映。範例中讀取的 serialize 設定也是如此。

對照目前的實作確認 Store 契約

請實作 Laravel 13 的 Illuminate\Contracts\Cache\Store 所要求的方法。不要將過去範例中的空方法清單直接當成可運作的驅動發布。 null 會被 Repository 視為快取未命中。實作時請不要將 false 或 0 也當成未命中。即使儲存 null,也無法作為防止 remember() 重新計算的標記。

透過 Repository 驗證 TTL

Repository::put() 會在傳給 Store 之前處理 TTL。
範例中的 ArrayStore 若直接對 put() 傳入 0 秒,會當成永久儲存。另一方面,Repository 的 put(..., 0) 則是刪除。只測試以 getStore() 取出的低階 Store,無法確認使用者實際呼叫的 API 行為。

add、鎖與標籤需另外確認支援情況

僅實作 Store 契約,並不保證支援所有快取功能。
  • 原子性的 add():不包含在 Store 契約中。以正的 TTL 呼叫時,若 Store 有 add(),Repository 就會使用它。若沒有,則會退回以 get() 與 put() 實現,因此僅以 Repository 包裝無法保證原子性。在共享後端中,請以該後端的原子操作實作,並驗證並行執行。
  • 鎖:Illuminate\Contracts\Cache\LockProvider 的 lock() 與 restoreLock() 是另一個契約。請驗證取得、擁有者一致、過期,以及不會釋放其他擁有者的鎖。範例中 ArrayStore 的鎖無法提供跨行程的互斥控制。
  • 標籤:Repository 會確認 Store 是否有 tags()。一般需要使用 TaggableStore 等進行實作,僅靠 Store 契約不會自動啟用。
請不要認為 flush() 是會尊重設定中 prefix 的部分刪除。請確認在共享後端中的刪除範圍,並連同 php artisan cache:clear 一起向使用者說明。

區分實例的保留與資料的刪除

CacheManager 是 singleton,store('catalog') 的結果也會依儲存名稱保留。僅變更設定或重新註冊 extend(),並不會更新已建立的 Repository。
$same 與 $before 是同一個物件。在 forgetDriver() 之後取得時,才會依新的設定建立 $after。 forgetDriver() 並不會刪除後端的所有資料。不過,範例中的 ArrayStore 將資料保存在實例內,因此新的 Store 看不到舊資料。此外,使用端的 $before,以及已解析的 cache.store singleton 所保留的舊 Repository 參照,都不會被替換。 在正式環境中,請依照一般的部署流程更新設定快取,並重新啟動長時間執行的 worker。也請考量服務的生命週期,不要將重新解析視為所有行程的一次性更新。

不只模擬 Facade,也要測試建立路徑

在套件的測試環境中,讓處理實際經過 Manager 與 Repository。下面的 Testbench 範例會確認,在先註冊的另一個提供者的 boot() 中也能使用該驅動。範例在不變更 cache.serializable_classes 還原限制的情況下儲存陣列,並以 ArrayStore::all(false) 確認原始的儲存值。
實作自訂後端時,除此之外還要驗證過期、many() 的未命中鍵、false 與 0 的往返、touch()、連線故障、並行更新,以及 prefix 與刪除範圍。不要將單純的 Cache::shouldReceive('get') 成功,當成驅動註冊或與外部服務相容的證據。

長期維護的檢查清單

  • 將驅動名稱、儲存設定、序列化格式、例外與失敗時的回傳值列為公開規格。
  • 依支援的 Laravel 版本,確認 Store 契約的方法,以及標籤、鎖等選用功能。
  • 驗證在有設定快取的全新啟動下也能註冊,且能從其他提供者的 boot() 中使用。
  • 變更儲存資料的相容性時,連同既有鍵的遷移或捨棄方針一併公告。
  • 區分行程內的實例保留、後端的資料,以及設定快取。

參考的一手資料

最後修改於 2026年10月29日