PHP 8.5 clone() 在 readonly 对象中更新属性的处理方法
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() 中改属性
修改发生在新副本的克隆阶段,不会回写原对象。应用层仍应限制入口并维护自己的业务不变量。
PHP 反序列化后为什么得到 __PHP_Incomplete_Class
- 上一篇
- PHP 反序列化后为什么得到 __PHP_Incomplete_Class
- 下一篇
- PHP 8.5 closure capture 怎样避免变量误绑定
-
- 文章 · php教程 | 25分钟前 |
- PHP fibers 怎样把阻塞任务接入调度器
- 474浏览 收藏
-
- 文章 · php教程 | 29分钟前 | 性能排查 · php教程 · php OpCache PHP性能 缓存命中 opcache_get_status opcache_is_script_cached
- PHP OPcache 怎样查看脚本缓存命中状态
- 195浏览 收藏
-
- 文章 · php教程 | 32分钟前 |
- PHP PDO 怎样区分预处理模拟和原生预处理
- 363浏览 收藏
-
- 文章 · php教程 | 35分钟前 | 闭包 · 语法 · PHP · PHP 8.5 · 闭包 返回值 callable 管道操作符 PHP 8.5 首类 callable pipe operator
- PHP 8.5 pipe 操作符怎样串联返回值
- 284浏览 收藏
-
- 文章 · php教程 | 36分钟前 |
- PHP 8.5 closure capture 怎样避免变量误绑定
- 379浏览 收藏
-
- 文章 · php教程 | 40分钟前 | 反序列化 · 故障排查 · php教程 · php 自动加载 unserialize __PHP_Incomplete_Class allowed_classes
- PHP 反序列化后为什么得到 __PHP_Incomplete_Class
- 199浏览 收藏
-
- 文章 · php教程 | 1天前 |
- PHP get_declared_interfaces 怎样列出当前已声明的接口名称
- 403浏览 收藏
-
- 文章 · php教程 | 1天前 |
- Laravel queue middleware 怎样增加重试间隔
- 250浏览 收藏
-
- 文章 · php教程 | 1天前 |
- Composer repositories canonical 设置怎样影响私有包解析
- 209浏览 收藏
-
- 文章 · php教程 | 1天前 |
- PHP ReflectionAttribute 按接口筛选 Attribute 的实现方式
- 157浏览 收藏
-
- 文章 · php教程 | 1天前 |
- PHP PharData 怎样向 TAR 归档追加文件
- 493浏览 收藏
-
- 文章 · php教程 | 1天前 |
- PHP DOMDocument 怎样保留节点中的空白文本
- 348浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 933次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 938次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1047次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1019次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 846次使用
-
- GO语言结构体面向对象操作示例
- 2023-01-07 127浏览
-
- golang实现PHP数组特性的方法
- 2023-02-16 371浏览
-
- PHP与Go语言之间的通信详解
- 2023-01-07 347浏览
-
- php和go语言的区别有哪些
- 2023-03-04 112浏览
-
- HTTP 的 response 中的响应体和头部是分开发送的吗?
- 2023-01-28 387浏览

