发布于2026-07-08 阅读(0)
扫一扫,手机访问
在开发中遇到 Cache::get() 返回 null,是件挺让人头疼的事。很多时候,问题根源并不复杂,就是缓存驱动根本没切换成功,你却浑然不知。
一个最直接的排查方法:执行下 php -r "var_dump(cache('test', 'ok', 10)); var_dump(cache('test'));"。如果第二次输出 bool(false),那基本就能断定,缓存链路压根就没通。这时候,你需要逐一确认三处硬性配置:
config/cache.php 文件,确保 default 的值是小写的 'redis',不是 'Redis' 或 'REDIS',这些细节是会“静默”失效的。stores.redis 下的配置块必须完整。特别注意 select 参数的值必须是整数,如果写成字符串 "0",一样会失败。另外,即便没有密码,password 也得显式写成空字符串 ''。php -m | grep redis,或者看 phpinfo() 页面里有没有 Redis 模块,这是最直接的证据。_cache() 这个方法有点特殊。很多人误以为它是 Db 类的一个内置方法,其实不然。它是查询构造器的一个“钩子”,只在 select、find、value 这些执行方法前调用才有效。如果你写成 Db::table('user')->where()->_cache()->select(),大概率会直接报“方法未定义”的错误。
正确的姿势应该是这样:
Db::table('user')
->where('status', 1)
->_cache(['user_active_list', 1800, 'user'])
->select();
这里有三个关键点需要留意:
_cache() 的参数必须是数组,且顺序固定为:[缓存键, 过期秒数, 标签]。传个字符串或数字进去是无效的。'user_list_' . md5($status . '_' . $type),这样能避免不同参数组合互相干扰。Cache::tag('user')->clear() 一次性清除所有用户相关的缓存。很多新手会这么写:UserModel::where('status', $status)->cache(true)->select()。粗看没问题,但实际上生成的缓存键只包含了类名和方法名(像 think:UserModel:select),这意味着所有不同的 $status 值都会共用同一个缓存,最终导致脏数据。
要实现按条件隔离缓存,有两种靠谱的做法:
cache(['user_list', $status, $type], 300),ThinkPHP 会自动帮你序列化成一个稳定的字符串。cache('user_list_' . md5($status . '_' . $type), 300),这种方式更可控,排查问题时也更直观。with('profile') 这种关联预载入会自动加入缓存键,它不参与键的生成。查询 with('profile') 和 with('posts') 的结果可能会互相污染。Cache::remember('user_'.$id, 3600, function () use ($id) { return User::find($id); }) 这种写法是安全的。但如果你漏掉了 use ($id),写成 function () { return User::find($id); },问题就来了。ThinkPHP 6 会把整个闭包序列化后作为 key 的一部分,导致每次执行都生成一个不同的 key,结果就是“写进去了,但读不出来”。
还有个更隐蔽的坑是序列化失败。如果闭包里面用到了 $this、Request 或 Query 这样的对象,serialize() 会悄悄失败,缓存的值就变成了空。一个通用的解决方案是:写入前先用 json_encode() 处理,读取时再用 json_decode() 还原,这样可以绕过 PHP 序列化的那些限制。
最后,一个最容易被忽略的细节:缓存键里不要包含空格或中文,尤其在 file 驱动下,这会导致文件写入失败且没有任何报错。最稳妥的兜底方案,就是统一用 md5($key) 来处理,或者只保留字母、数字和下划线。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8