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 中。
- 在常规启动中,应用的
booting会在所有服务提供者的register()完成之后、各个boot()之前执行。它与服务提供者自身的$this->booting()不同。 extend()的回调会接收应用实例和存储配置数组。Store 的创建和外部连接应放在这个创建回调中。- 这一注册时机并不保证其他服务提供者在
register()中使用缓存也能生效。请将服务的注册与实际使用分离。
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配置未被禁用,则为 Repository 设置 Dispatcher。
serialize 配置也是如此。
对照当前实现检查 Store 契约
请实现 Laravel 13 的Illuminate\Contracts\Cache\Store 所要求的方法。不要将旧示例中的空方法列表直接作为可用的驱动发布。
null 会被 Repository 视为缓存未命中。实现时不要把 false 或 0 也当作未命中。即使保存了 null,也无法作为阻止 remember() 重新计算的标记。
通过 Repository 验证 TTL
Repository::put() 会在传给 Store 之前处理 TTL。
单独确认 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。还要考虑服务的生命周期,不要将重新解析视为对所有进程的统一更新。
不只 mock 门面,还要测试创建路径
在包的测试环境中,让测试经过真实的 Manager 和 Repository。下面的 Testbench 示例确认了在先注册的另一个服务提供者的boot() 中也能使用该驱动。示例在不更改 cache.serializable_classes 还原限制的前提下保存数组,并通过 ArrayStore::all(false) 确认原始保存值。
many() 的未命中键、false 和 0 的往返、touch()、连接故障、并发更新,以及 prefix 与删除范围。不要将简单的 Cache::shouldReceive('get') 成功当作驱动注册或与外部服务兼容的证据。
长期维护检查清单
- 将驱动名称、存储配置、序列化格式、异常及失败时的返回值作为公开规范。
- 针对每个支持的 Laravel 版本,确认 Store 契约的方法以及标签、锁等可选功能。
- 验证在启用配置缓存的全新启动中也能完成注册,并能在其他服务提供者的
boot()中使用。 - 对保存数据的兼容性变更,应连同现有键的迁移或废弃方针一并公告。
- 区分进程内的实例保留、后端的数据以及配置缓存。