PHP 8.5 clone() 在 readonly 对象中更新属性的处理方法
来源:17golang原创
时间:2026-11-08 00:26:20 436浏览 收藏
PHP 8.5 的 clone() 函数可以在复制对象时接收第二个参数,用数组覆盖副本中的指定属性。对 readonly 类来说,这正好适合封装 withX() 方法:原对象继续保持不变,新对象只替换允许变化的字段。需要注意的是,覆盖发生在 __clone() 之后,构造函数不会重新执行,所以领域校验要放在克隆前或属性 hook 中。
官方地址:https://www.php.net/manual/en/language.oop5.cloning.php
clone($object, ['field' => $value])只改变返回的副本,未列出的属性继续从原对象复制。__clone()先运行,之后才应用withProperties;不要把构造函数当成再次校验点。- 业务方法应限制可覆盖字段并先检查新值,避免生成违反不变量的 readonly 副本。
PHP 8.5 clone() 解决的是什么问题
传统写法 clone $point 只能得到一个字段完全相同的副本。如果类是不可变对象,想实现“只改 x,y 保持原值”的 withX(),旧版本通常要重新调用构造函数,或在 __clone() 中安排一次修改。PHP 8.5 把属性覆盖作为 clone() 的第二参数,读者可以把复制和局部变化写在同一处。
| 写法 | 作用 | 边界 |
|---|---|---|
clone $obj | 复制对象当前状态 | 没有属性覆盖数组 |
clone($obj, $map) | 复制后覆盖列出的属性 | 只应放入明确允许修改的字段 |
__clone() | 定义克隆时的对象级处理 | 在属性覆盖之前执行 |
用 withX 方法生成 readonly 新副本
把覆盖动作封装在类内部,既能控制字段白名单,也能让调用方继续使用不可变对象。下面的例子只允许修改 x,y 始终从原对象复制。
$x]);
}
}
$origin = new Point(10, 20);
$moved = $origin->withX(30);
// 结果:origin 仍是 10,20;moved 是 30,20。
var_dump($origin->x, $origin->y);
var_dump($moved->x, $moved->y);
?>
withProperties 只覆盖数组中列出的属性,因此这里没有重新构造 Point,也没有改变 $origin。生产代码中建议把数组写死在领域方法里,不要直接把外部输入数组透传给 clone()。

__clone 顺序和构造校验边界
需要记住两个顺序:先执行对象的 __clone(),再应用第二参数中的属性覆盖;复制过程不会再次调用构造函数。也就是说,构造函数里做过的校验不会自动拦截新的覆盖值。
如果字段有业务不变量,应在 withX() 里先校验,再调用 clone()。下面的写法把“分数必须在 0 到 100”作为领域方法的前置条件:
100) {
throw new InvalidArgumentException('score must be between 0 and 100');
}
// 通过白名单只更新 score,其他状态从原对象复制。
return clone($this, ['score' => $score]);
}
}
$result = new ExamResult(86);
$updated = $result->withScore(92);
echo $updated->score; // 92
?>
如果属性需要从多个入口修改,校验不应只依赖某一个便利方法,还要结合属性 hook、公开性设计或更窄的工厂接口。尤其不要把“readonly”误解成“所有复制结果都天然合法”:它保护的是属性写入语义,不替你定义业务规则。

接入项目时的四项检查
- 版本:确认运行环境是 PHP 8.5 或更高版本;低版本不能直接使用带第二参数的
clone()。 - 字段:为每个
withX()明确字段白名单,不把请求数组直接作为覆盖映射。 - 可见性:覆盖操作要放在有权限访问目标属性的类方法中,避免在类外部猜测私有状态。
- 不变量:把范围、状态转换和关联字段检查放到克隆前,并为原对象未变化、新副本变化和非法输入分别写测试。
一个简单的回归断言至少应同时检查 $origin 与 $updated:前者必须保持原值,后者只允许出现预期字段变化。这样才能把“复制成功”和“领域对象仍然有效”区分开。
相关问题
PHP 8.5 的 clone() 会重新调用构造函数吗
不会。对象复制使用已有实例状态,__clone() 会在属性覆盖前执行;如果新值需要校验,应显式放在克隆前。
withProperties 没列出的字段会怎样
它们继续从原对象复制。这个特性适合局部更新,但也意味着关联字段不会因为某一个字段变化而自动同步。
readonly 对象为什么还可以在 clone() 中改属性
修改发生在新副本的克隆阶段,不会回写原对象。应用层仍应限制入口并维护自己的业务不变量。
-
127 收藏
-
371 收藏
-
347 收藏
-
112 收藏
-
387 收藏
-
474 收藏
-
文章 · php教程 | 17分钟前 | 性能排查 · php教程 · php OpCache PHP性能 缓存命中 opcache_get_status opcache_is_script_cached195 收藏
-
363 收藏
-
文章 · php教程 | 22分钟前 | 闭包 · 语法 · PHP · PHP 8.5 · 闭包 返回值 callable 管道操作符 PHP 8.5 首类 callable pipe operator284 收藏
-
379 收藏
-
文章 · php教程 | 28分钟前 | 反序列化 · 故障排查 · php教程 · php 自动加载 unserialize __PHP_Incomplete_Class allowed_classes199 收藏
-
403 收藏
-
250 收藏
-
209 收藏
-
157 收藏
-
493 收藏
-
348 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习