编程 PHP 8.5 的 clone-with:readonly 对象更新属性不再需要手写模板

2026-09-02 21:05:58

PHP 8.5 为 clone 引入了带属性的克隆写法:clone($obj, ['name' => 'new'])。第二个参数是关联数组,键名对应要更新的属性,键值就是新值。该调用会返回一个克隆对象,同时将该数组中的属性覆盖写入新对象,而不是修改原对象。

在 8.5 之前,readonly 属性只能在构造期间赋值一次。要“修改”一个不可变对象,通常只能 new 一个对象并复制所有字段,或者通过 clone 加反射绕过限制,两种方式都繁琐。8.5 的 clone() 第二参数本质上是在构造路径上给属性赋初值,所以对 readonly 属性同样生效。

一个典型场景是金额值对象:

final readonly class Money
{
    public function __construct(public int $amount, public string $currency) {}

    public function withAmount(int $amount): self
    {
        return clone($this, ['amount' => $amount]);
    }
}

调用时只需:

$m2 = $money->withAmount(500);

clone 再逐个赋值相比,这种写法只更新你列出的属性,其余字段原样拷贝,适合大多数不可变对象的局部更新。如果字段很多且变更频繁,为每个字段写一个 withXxx() 仍然是模板代码,这时可以考虑让 with() 方法直接接收关联数组,内部依然返回一个 clone

需要注意两个边界:

  1. 关联数组中的键必须是对象的构造器或属性提升中已有的属性名,传入未知键会导致异常。所以调用时只传想改的字段,不要额外塞元数据。
  2. 如果对象同时有常规属性和提升属性,或者构造时存在一些初始化顺序,要确认 clone() 的覆盖行为与预期一致。readonly 属性在这种情况下被允许重新赋值,仅因为它在克隆路径中仍以“首次构造”的方式处理。

这个语法主要面向 readonly DTO、事件对象、配置快照这类不可变值。8.5 之前没有这个语法,现有项目不能不加修改地直接使用,升级时要注意检查代码里是否有自定义的 __clone() 或对 clone 关键字的特殊依赖,测试对像重构或反序列化是否受影响。

复制全文 生成海报 PHP 编码技巧 8.5新特性 面向对象

推荐文章

程序员茄子在线接单