PHP 8.3反序列化绕过构造函数直接写入只读属性,触发“Cannot modify readonly property”致命错误;必须实现__unserialize()并调用构造函数重建状态,Java 17则允许JVM绕过final约束完成反序列化。

PHP 8.3对象序列化:绕过构造函数直接填充属性
PHP 8.3 的 serialize() → unserialize() 流程不调用 __construct(),而是直接向对象属性写入值,这是语言底层实现决定的。只读属性在反序列化时会触发 【Fatal error: Cannot modify readonly property】,因为 PHP 强制执行只读语义,不允许绕过构造函数赋值。
即使你显式定义了 public readonly string $id,在 unserialize() 执行瞬间,引擎尝试直接写入 $this->id 就会中断流程并报错。
要让只读属性通过反序列化存活,必须手动实现 __unserialize() 方法,并在其中调用 $this->__construct(...) —— 这是唯一合法重建只读状态的路径。
Java 17对象序列化:依赖 Serializable 接口与无参构造器
Java 17 要求被序列化的类必须实现 java.io.Serializable 接口,且反序列化时默认调用类的无参构造器(如果存在)。若类没有无参构造器,或构造器被 private 修饰,反序列化将抛出 InvalidClassException。
立即学习“PHP免费学习笔记(深入)”;
Java 不限制字段是否“只读”,final 字段在反序列化过程中会被特殊处理:JVM 允许在反序列化阶段绕过 final 语义,直接写入值,只要字段不是 static 或 transient。这与 PHP 8.3 对 readonly 的严格拦截形成鲜明对比。
注意:Java 中的 readObject() 方法可自定义反序列化逻辑,但需声明为 private void readObject(ObjectInputStream) 并配合 ObjectInputStream.defaultReadObject() 调用,否则字段不会被恢复。
关键差异对比:三处不可妥协的分歧点
第一,初始化机制不同:PHP 反序列化完全跳过构造函数;Java 反序列化默认依赖无参构造器(除非使用 serialPersistentFields 或自定义 readObject)。
第二,只读语义执行强度不同:PHP 8.3 在反序列化阶段仍强制 readonly 约束,写入即崩;Java 17 对 final 字段采取“反序列化豁免”策略,允许 JVM 直接覆写。
第三,类型系统介入程度不同:PHP 的 readonly 是语言级语法约束,反射也无法绕过(ReflectionProperty::setValue 会对 readonly 属性抛出 Error);Java 的 final 是编译期+运行期联合约束,但 ObjectInputStream 内部使用 Unsafe.allocateInstance 绕过构造器,从而规避 final 检查。
如果你在 Java 中定义了 private final String token,并在 writeObject 中未显式保存它,该字段在反序列化后将为 null —— PHP 里不存在这种“自动丢弃”,所有 public/protected 属性(含 readonly)只要出现在序列化字符串中,就一定会被尝试写入,只是 PHP 会因只读限制而失败。



















