readonly属性在PHP 8.1+中初始化后不可赋值,克隆仅浅拷贝且不改变该约束;其嵌套对象或数组的修改仍影响原对象,深拷贝需手动实现__clone()或__unserialize()。

readonly 属性在 PHP 8.1+ 中一旦初始化就不可再写,克隆后仍受此约束——不是拷贝方式的问题,而是语言层的访问控制。浅拷贝和深拷贝的区别,依然落在“引用是否共享”上,跟 readonly 无关。
clone 操作对 readonly 对象只做浅拷贝
PHP 的 clone 关键字默认就是浅拷贝:它会新建一个对象实例,但所有属性(包括 readonly)的值照搬原对象。如果某个属性是对象或数组,新旧对象仍指向同一内存地址。
-
readonly属性本身不能被赋值,无论在原对象还是克隆对象里,$obj->prop = ...都会报Fatal error: Cannot assign to readonly property - 如果
readonly属性是数组,$clone->arr[] = 1允许(因为没改属性本身,只是调用其方法),但$clone->arr = []不允许 - 如果
readonly属性是对象,$clone->obj->name = 'x'可以成功(改的是那个对象的内部状态,不是obj属性本身)
想让克隆后的对象拥有独立可变的嵌套结构?必须手动深拷贝
PHP 没有内置的通用深拷贝机制,clone 不会自动递归处理 readonly 属性里的对象或数组。要真正隔离,得靠 __clone() 手动干预:
- 在类中定义
__clone()方法,在里面对每个需要隔离的引用类型属性显式clone或重建 - 如果
readonly属性是array,无法直接clone $this->arr(数组不支持clone),得用$this->arr = array_merge([], $this->arr)或递归复制 - 如果
readonly属性是自定义对象,且该对象也支持clone,则可在__clone()中写$this->obj = clone $this->obj - 注意:
__clone()在clone后立即执行,但它不能修改readonly属性本身(比如$this->prop = ...),只能调用其方法或操作其内容
为什么 serialize/unserialize 对 readonly 失效?
unserialize() 创建的是全新对象,绕过了 __construct(),但 PHP 8.1+ 规定:反序列化时若目标类含 readonly 属性,且未在 __unserialize() 中显式初始化,会直接报错 Fatal error: Readonly property must be initialized。
立即学习“PHP免费学习笔记(深入)”;
- 简单
unserialize(serialize($obj))会失败,除非类实现了__unserialize()并手动给每个readonly属性赋值 - 这意味着你不能靠序列化“偷懒”实现深拷贝,尤其当对象结构复杂、含多个
readonly属性时,__unserialize()的维护成本很高 - 更现实的做法是:避免把可变嵌套结构塞进
readonly属性;或者改用普通属性 + 构造后封禁(如私有 setter + 文档约定)
最易被忽略的一点:readonly 不等于“不可变”,它只禁止对属性本身的赋值操作。嵌套对象内部状态、数组元素增删、资源句柄操作,全都不受限制——所以你以为“克隆后安全了”,其实可能正悄悄污染原始数据。



















