PHP 反序列化后为什么得到 __PHP_Incomplete_Class
来源:17golang原创
时间:2026-11-08 00:22:15 199浏览 收藏
PHP 反序列化后出现 __PHP_Incomplete_Class,通常不是对象属性突然丢失,而是 PHP 找不到序列化数据所指向的原类,或者 allowed_classes 明确把这个类排除在外。先记住判断顺序:看序列化数据里的类名,再看当前请求是否加载了这个类,最后看反序列化选项是否允许它。
官方手册:https://www.php.net/manual/en/class.php-incomplete-class.php
这个占位对象表示“对象数据读到了,但原来的业务类没有按当前规则恢复”。修复重点是补齐类加载或调整明确的白名单;如果数据来自用户或外部系统,不要为了恢复对象而放宽到任意类,优先改用 JSON 等数据格式。
- 类未定义、命名空间不一致或自动加载没有生效,会让对象退化为不完整对象。
allowed_classes为false,或数组里没有目标类,同样会产生不完整对象。unserialize()返回false并伴随 Warning,属于输入格式失败,和不完整对象不是同一条分支。
先看它为什么不是原来的业务类
序列化对象时,字符串中会带有类名。反序列化时,PHP 需要根据这个名字找到当前进程中的类定义;如果类不存在,就只能保留已读到的属性,并用 __PHP_Incomplete_Class 表示“没有完成业务类恢复”。这个类不是应该由业务代码主动创建的模型类,而是 PHP 为异常恢复状态提供的占位。

可以先用一个不存在的类名观察这个现象。下面的字符串只用于说明诊断边界,不代表生产代码应该手工拼接序列化数据。
__PHP_Incomplete_Class_Name); } ?>
如果输出的原始类名是 MissingProfile,说明排查方向应放在类文件、命名空间和自动加载,而不是先去修改属性名。实际项目中也可能是发布顺序造成的:写入数据的服务已经部署了新类,读取数据的服务还没有加载对应版本。
把类未加载和白名单排除分开
第二个常见原因是调用方主动限制了可恢复的类。allowed_classes 可以是布尔值或类名数组;设置为 false 时不接受对象类,设置为数组时只接受列出的类。目标类没有出现在数组里,即使类文件已经被加载,也会得到 __PHP_Incomplete_Class。
false]); var_dump(get_class($blocked)); // 只允许名单中的类;名单不包含 UserProfile 时同样会被排除。 $limited = unserialize($payload, ['allowed_classes' => [DateTimeImmutable::class]]); var_dump(get_class($limited)); ?>
这一步最容易误判:如果把所有问题都归因于自动加载,就会漏掉调用方的安全配置;如果为了让旧代码继续运行而直接把白名单改成“全部允许”,又会失去类过滤的意义。诊断时应把“类是否已加载”和“规则是否允许”分别记录。
按三个证据点定位原因
排查不需要猜测,可以固定看三个证据点:
- 序列化数据中的类名:从
__PHP_Incomplete_Class_Name或数据生产端的类定义确认完整类名,特别注意命名空间大小写和反斜杠。 - 当前进程的类加载状态:检查 Composer 自动加载入口是否在反序列化之前执行,或者确认手工
require_once的文件确实定义了同名类。 allowed_classes规则:沿调用链找到真正执行unserialize()的位置,确认是否为false,或数组中是否漏掉了目标类。

如果三项都正常,却仍然出现不完整对象,再检查读取端和写入端是否使用了不同命名空间、不同版本的类名,或者当前请求是否走了另一条缓存/队列消费路径。不要只在数据库里搜索属性值;这个问题首先是类解析问题。
修复时优先恢复明确的类边界
对于可信的内部存量数据,修复通常有两步:在反序列化之前完成自动加载,并只把业务上确实需要恢复的类放入白名单。下面示例用类型判断阻止不完整对象继续进入业务逻辑。
[UserProfile::class],
]);
// 类型不符合预期时立即停在边界,避免继续访问业务属性。
if (!$value instanceof UserProfile) {
throw new UnexpectedValueException('对象类型不是 UserProfile');
}
echo $value->name;
?>
如果数据来自请求参数、Cookie、上传文件或不受信任的外部系统,不应把“恢复成功”当成目标。PHP 手册明确提醒不要对不可信输入使用 unserialize();这类场景应把数据模型改成 JSON,并在读取后做字段和类型校验。allowed_classes 是缩小对象恢复范围的措施,不是把不安全输入变安全的替代品。
最后用类型和原始类名复查
修复后不要只看页面没有报错,而要确认返回值已经是预期类。可以保留一段很小的边界记录:业务对象进入服务层前,使用 instanceof 判断;遇到不完整对象时记录原始类名和数据来源,不要把它重新序列化后继续传递。
name;
} elseif ($value instanceof __PHP_Incomplete_Class) {
// 原始类名用于定位加载或白名单问题,不用于绕过限制。
$className = $value->__PHP_Incomplete_Class_Name ?? 'unknown';
error_log('Incomplete serialized class: ' . $className);
} else {
// 其他类型可能来自错误输入或版本不兼容,需要单独处理。
throw new UnexpectedValueException('反序列化结果类型不符合预期');
}
?>
常见问题
出现 __PHP_Incomplete_Class 是不是说明序列化字符串损坏?
不一定。它更常见地表示类未定义或被 allowed_classes 排除;字符串格式损坏通常会走返回 false 和 Warning 的错误分支。
把 allowed_classes 改成 true 就能解决吗?
它可能绕过白名单排除,但不应该作为通用修复。先确认目标类、自动加载和数据来源,再按最小范围列出允许恢复的类。
能不能直接 new __PHP_Incomplete_Class?
不要这样设计业务代码。它是 unserialize() 在特定条件下创建的占位状态,正确做法是修复类加载/白名单边界,或改用适合外部数据的格式。
为什么开发环境正常,线上却出现这个对象?
重点比较两边的 Composer 自动加载入口、命名空间、部署版本和 allowed_classes 配置。写入端和读取端的类名不一致,也会造成同样现象。
小结
__PHP_Incomplete_Class 可以理解为 PHP 在反序列化过程中的“保留数据、暂停恢复”状态。先拿到原始类名,再确认类是否已加载,最后检查 allowed_classes;对可信内部数据修复明确的加载和白名单,对不可信外部数据则优先停止使用 unserialize(),改用 JSON 等数据交换格式。
-
487 收藏
-
313 收藏
-
196 收藏
-
306 收藏
-
393 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习