登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP readonly处理只读属性的克隆边界的实现方法

来源:17golang原创

时间:2026-09-15 23:02:37 473浏览 收藏

我第一次把值对象改成 readonly 时,最容易误判的一点就是:既然属性不能改,clone 后是不是也只能原样复制?答案是,PHP 8.3 起,类的 __clone() 方法里可以对已初始化的 readonly 属性做一次重新初始化。这个窗口服务于“复制后替换内部对象”这类场景,但它不是一个通用 setter,仍然受类作用域、类型和直接赋值规则约束。

实现 readonly 对象克隆时,把 __clone() 当成一次受限的复制钩子:标量可以按需写回,嵌套对象要显式 clone 才能断开共享,外部代码不能借此绕过可见性。
要点速览
  • 普通方法和外部作用域不能重复赋值,__clone() 只打开一次重新初始化窗口。
  • 标量、数组通常按值复制;readonly 对象属性若不显式 clone,内部对象仍可能共享。
  • PHP 8.4 起,获取 readonly 属性引用等间接修改方式在 __clone() 中也不允许。

readonly 的克隆例外到底打开了什么

平时的规则很简单:readonly 属性只能在声明它的类作用域内初始化一次,之后即使写入完全相同的值,也会抛出 Error。克隆时 PHP 先创建副本,再调用副本的 __clone();从 PHP 8.3 开始,这个方法内部允许把 readonly 属性重新初始化一次。

因此,下面的写法表达的是“复制对象时换一份内部载荷”,而不是把属性变成可变字段:

payload = clone $this->payload;
    }
}

$copy = clone $document; // __clone() 在副本内部执行一次写回
?>

这里的关键不是“readonly 被解除”,而是 PHP 为克隆过程提供了一个受控的重新初始化点。赋值仍然发生在 Document 的类作用域内,类型也必须匹配;在类外直接给 $copy->payload 赋值依旧会失败。

PHP readonly __clone 类内作用域和一次重新初始化写回的结构说明图
图1:PHP readonly 克隆窗口的结构说明图,展示一次写回发生的作用域边界。

对象属性要不要 clone,决定的是深拷贝还是浅拷贝

readonly 约束的是“属性槽位不能被重新指向”,不等于属性值指向的对象内部永远不变。克隆对象时,标量按值复制,数组也按值语义复制;但对象属性保存的是对象句柄,默认复制可能让原对象和副本共同指向同一个内部对象。

属性内容不写 __clone() 时实现建议
字符串、整数、布尔值副本拥有自己的值通常无需处理
数组按值语义复制,不能直接改 readonly 数组先确认是否真的需要重建
对象可能共享同一个内部对象在 __clone() 中显式 clone
外部作用域赋值仍受可见性和 readonly 规则拦截提供类内 with 方法或构造新对象

这也是为什么深拷贝不能只看外层的 clone 关键字。要复制的是对象图,就要沿着需要独立的节点继续复制;不需要独立的只读服务、缓存或共享配置,则不必为了“看起来完整”而递归 clone。

PHP readonly 标量数组对象属性从共享对象到显式 clone 独立副本的关系说明图
图2:readonly 属性深浅拷贝关系说明图,突出嵌套对象的共享与断开。

一次克隆流程里,先解除共享再写回

我更愿意把 __clone() 写成一份短小的对象图策略:只列出必须独立的对象属性,逐个用 clone 生成新节点,再直接写回当前副本。这样读代码的人能马上看出哪些状态会被共享,哪些状态会随副本分离。

如果属性是值对象且本身已经不可变,浅复制可能足够;如果对象内部有可变数组、句柄或缓存,就要先确认它是否属于实例状态。把所有对象都无脑 clone 可能破坏共享资源语义,也会增加成本。克隆策略应服务于对象的不变量,而不是追求“每个引用都不同”。

容易踩坑的三条边界

  • 把例外当 setter:__clone() 只服务于克隆副本,不能从普通方法里重复给已初始化属性赋值。
  • 忘记作用域:公开可读不代表公开可写;外部代码修改 protected/private 或默认受保护写入的 readonly 属性会报错。
  • 使用间接修改:PHP 8.4 起,在 __clone() 中通过获取引用等方式间接修改 readonly 属性不再允许,使用直接赋值或直接替换对象。

测试时至少断言三件事:克隆后需要独立的嵌套对象不是同一实例;原对象的属性值未被改写;从类外重新赋值会得到预期的 Error。如果项目还支持 PHP 8.2,不能依赖 8.3 的 readonly 克隆语义,应改用构造新对象或升级运行时。

常见问题

PHP readonly 属性在 __clone() 中能改几次?

针对一个属性,克隆过程提供的是一次重新初始化机会。把逻辑集中成一次直接赋值,避免在多个辅助方法中重复写入。

readonly 对象属性是不是天然深拷贝?

不是。readonly 只限制属性重新指向,内部对象仍可能被共享;需要独立状态时,在 __clone() 中显式复制该对象。

为什么获取 readonly 属性的引用会失败?

这是间接修改,不是对属性槽位的普通直接赋值。PHP 8.4 起该方式在 __clone() 中也被禁止,应改为一次直接写回。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>