ThinkPHP缓存序列化失败须先检测不可序列化类型(如Closure、PDO),再按驱动选择JSON序列化(Redis)或重写serialize跳过非法值(File),最终重构数据结构根治问题。

ThinkPHP缓存驱动数据序列化失败时,程序会直接抛出Serialization of 'Closure' is not allowed或Exception: Serialization of 'PDO' is not allowed等致命错误,导致接口崩溃、页面白屏,无法继续执行后续逻辑。
确认是否含不可序列化类型
第一步:在缓存前加临时检测,用var_dump(array_filter($data, function ($v) { return !is_serializable($v); }, ARRAY_FILTER_USE_BOTH));快速定位值中含Closure、PDO、mysqli、资源句柄或未实现__sleep()的自定义对象。
第二步:检查是否把Request、Response、Db::name()->select()返回的集合对象(含查询器)直接塞进cache()——这些对象内部持有了不可序列化的运行时依赖。
这一步操作起来很简单,直接把检测代码贴到缓存调用前就行。不这样做,你永远不知道哪条数据在上线后突然触发报错。
立即学习“PHP免费学习笔记(深入)”;
Redis驱动启用JSON序列化(推荐)
方法一:修改config/cache.php中stores.redis配置项,添加'serialize' => 'json':
'redis' => [ 'type' => 'redis', 'host' => '127.0.0.1', 'port' => 6379, 'select' => 0, 'password' => '', 'timeout' => 3, 'read_timeout' => 3, 'serialize' => 'json' // ← 关键新增行 ]
方法二:若使用.env统一管理,追加REDIS_SERIALIZE=json并确保config/cache.php中读取该变量:'serialize' => env('REDIS_SERIALIZE', 'php')。
【注意:开启后原有serialize()缓存将无法读取,必须清空Redis中所有think:开头的key】否则get()会返回null或乱码。
File驱动重写serialize方法跳过非法值
第一步:创建app/common/cache/FilterFile.php,继承think\cache\driver\File:
第二步:重写serialize()方法,对不可序列化值统一转为null并记录日志:
protected function serialize($data) { if (is_object($data) || is_resource($data)) { error_log("Unserializable data detected in cache key: " . debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function']); return 'null'; } return parent::serialize($data); }
第三步:在config/cache.php中将default设为'filter_file',并在stores中注册该驱动:
'filter_file' => [ 'type' => 'app\common\cache\FilterFile', ]
这一步不能省略注册,否则框架找不到驱动类会静默回退到file,起不到过滤作用。
重构缓存数据结构(根治)
① 优先缓存数组而非对象:调用$user->toArray()而非$user本身;
② 剔除运行时字段:在模型中定义__sleep(),只保留id、name等业务字段,排除db、query等连接相关属性;
③ 拆分敏感结构:把含闭包的回调配置单独抽离,缓存时用字符串标识(如'pay_callback_v2'),运行时再动态绑定;
④ 禁止缓存第三方SDK client实例——它们几乎都含资源句柄,改用工厂模式按需生成。
这四步做完,cache('user_123', $user)就再也不会因序列化失败而中断请求。



















