PHP 8.2只读类不支持自动深拷贝,且无法通过__clone()修改属性;正确方式是提取原始数据并用构造函数重建新实例,确保嵌套只读对象链式还原,避免serialize/unserialize或反射破坏不可变性。

PHP 8.2 的只读类本身不提供自动深拷贝能力,且由于其属性不可修改,常规的 clone + 重赋值方式会失败。实现深拷贝需绕过只读限制,核心思路是:在新对象构造阶段完成完整数据复制,而非在 clone 后修改属性。
只读类无法直接 clone 后赋值
PHP 的 clone 操作会触发 __clone() 方法(如果定义),但只读类的所有属性在构造完成后即被锁定。即使你在 __clone() 中尝试赋值,也会抛出 Fatal error: Cannot modify readonly property。
例如:
readonly class Config {
public function __construct(
public string $host,
public array $options
) {}
public function __clone() {
$this->host = 'localhost'; // ❌ 运行时错误
$this->options = ['debug' => true]; // ❌ 同样报错
}
}
推荐做法:用构造函数重建副本
最安全、最符合只读语义的方式,是不依赖 clone,而是通过已有实例提取数据,调用新构造函数生成独立对象:
立即学习“PHP免费学习笔记(深入)”;
- 确保只读类的构造参数可被外部获取(如通过公共属性或 getter)
- 对嵌套引用类型(如数组、对象)手动深拷贝
- 用深拷贝后的数据新建只读实例
readonly class User {
public function __construct(
public string $name,
public array $roles,
public ?Address $address
) {}
// 提供安全的数据提取方法(非必需,但推荐)
public function toArray(): array {
return [
'name' => $this->name,
'roles' => $this->roles,
'address' => $this->address ? $this->address->toArray() : null,
];
}
}
readonly class Address {
public function __construct(public string $city, public string $zip) {}
public function toArray(): array { return get_object_vars($this); }
}
// 深拷贝示例
$original = new User('Alice', ['admin'], new Address('Beijing', '100000'));
// 手动深拷贝:递归处理嵌套结构
$copyData = $original->toArray();
$copyData['roles'] = array_values($original->roles); // 确保数组副本
if ($original->address) {
$copyData['address'] = $original->address->toArray();
}
// 重建新只读实例(关键:不修改原对象,而是新建)
$copy = new User(
$copyData['name'],
$copyData['roles'],
$copyData['address'] ? new Address($copyData['address']['city'], $copyData['address']['zip']) : null
);
对嵌套只读对象的支持
若只读类中包含其他只读对象(如 Address),只要这些嵌套类也遵循只读构造原则,就可以链式重建:
- 每个只读子对象都应能通过构造参数完全还原
- 避免依赖
serialize/unserialize—— 它虽能深拷贝,但会绕过构造逻辑,丢失类型约束和初始化校验,且对只读类支持不稳定 - 不建议使用
ReflectionProperty::setValue()强制写入 —— 破坏只读契约,违反设计初衷,且 PHP 8.2+ 对只读属性有运行时保护,反射写入会失败
为什么不推荐 serialize/unserialize?
虽然技术上可行,但存在明显风险:
- 反序列化可能跳过构造函数逻辑(如验证、规范化)
- 若只读类含资源、闭包或未实现
Serializable接口,会失败 - 破坏了只读类“构造即终态”的语义一致性
- PHP 8.2+ 对只读属性的底层保护机制可能在未来版本进一步限制此类操作



















