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

PHP 8.5 clone with更新只读对象

来源:17golang原创

时间:2026-11-15 13:57:18 428浏览 收藏

PHP 8.5 可以用 clone($object, ['属性名' => 新值]) 创建副本并覆盖指定属性,包括在合法作用域内更新 readonly 属性。原对象保持原值,调用者需要接住返回的新对象。“clone with”是这项能力的名称,实际代码使用函数式 clone() 与属性数组。

例如,一份后台任务配置已经被多个任务共用,现在只想为某一任务提高并发数。直接给原对象赋值会违背只读约束;合适的做法是通过类内的 withParallelism() 返回另一份配置。

PHP 官方地址:https://www.php.net/

明确 PHP 8.5 实际支持的写法

PHP 8.5 迁移说明将它列为 Clone function:第二个参数提供属性名称与新值的映射。数组没有列出的属性继续沿用复制得到的值。普通的 clone $object 仍然可用。

官方新特性说明:https://www.php.net/manual/en/migration85.new-features.php

不要把旧讨论稿里的带 with 关键字表达式直接写入项目。对于 readonly 属性,也不能由“读权限是 public”推断任意外部调用都可以覆盖它;设置可见性仍然约束写入。因此,下面将复制更新封装在声明类内部。

给只读配置对象增加受控更新方法

完整示例要求 PHP 8.5 或更高版本。类名、字段与业务范围均为本文原创示例;它只涉及标量配置,不包含数据库、网络或资源句柄。本节展示预期语义,不把示例描述成本机运行结果。

 8) {
            throw new InvalidArgumentException('并发数必须在 1 到 8 之间');
        }
    }

    public function parallelism(): int
    {
        return $this->parallelism;
    }

    public function withParallelism(int $value): self
    {
        // 克隆不重跑构造函数,更新前复用同一业务检查
        self::checkParallelism($value);
        return clone($this, ['parallelism' => $value]);
    }
}

$base = new WorkerOptions('reports', 2);
$changed = $base->withParallelism(5);

// 分别读取两个实例,不能丢弃更新方法的返回值
echo $base->parallelism(), PHP_EOL;
echo $changed->parallelism(), PHP_EOL;

按代码语义,两个读取结果应分别是2和5;$base 与 $changed 是不同实例,二者的 queue 均为 reports。这里特意将 parallelism 声明为 private,让更改只能经由类的方法表达业务意图。

WorkerOptions原对象、副本和属性覆盖映射的静态关系
图1:原创结构图,属性映射针对新副本;原对象与副本拥有各自的标量属性值,未列出的 queue 保留复制值。

保留业务校验并理解克隆钩子

克隆不会重新执行本例的构造函数,所以只在构造函数里限制并发数不够。示例把范围检查提取成共享方法,构造与更新入口都调用它。类型约束解决的是“是否为 int”,业务检查解决的是“这个整数是否在可接受范围内”。

官方克隆手册说明,存在 __clone() 时,属性覆盖发生在该钩子执行之后。因而不能简单地在钩子里检查旧值,就以为新传入的属性也经过同样检查。若已有克隆钩子处理派生状态,需要重新考虑它与覆盖字段的关系。

官方对象克隆说明:https://www.php.net/manual/en/language.oop5.cloning.php

受控方法还有一个好处:不必对外提供“任意属性数组”入口。字段间存在联动规则时,方法可以一次检查组合后的值,而不是让调用者各改一项后才发现对象状态不完整。若对象承担复杂领域约束,显式工厂或构造新实例可能更容易维护。

区分顶层只读与嵌套对象共享

普通对象克隆默认进行浅复制。如果一个配置字段保存的是可变对象,复制外层实例不意味着内部对象也变成独立副本。readonly 限制属性重新赋值,但不能据此认定该属性所指向对象的内部状态不可变。

本例刻意使用 string 与 int,避免把这种共享带入更新操作。如果后续添加可变的元数据对象,应明确它是否可以共享;需要隔离时,针对该成员设计复制策略,或者改为自身也不可变的值对象。不要为获得“深复制”而对任意对象盲目进行序列化往返。

官方 readonly 属性说明:https://www.php.net/manual/en/language.oop5.properties.php

只读属性约束与嵌套可变对象共享的静态边界
图2:原创说明图,外层实例独立与内部成员共享是两个问题;这是扩展场景的关系说明,不是主示例的运行结果。

为旧运行环境保留重建路径

如果项目仍需支持 PHP 8.2至8.4,可以保留 readonly 类,通过构造函数重建同类实例。下面的方法用于替换主示例中的同名方法,不能与原方法同时声明。

// 将此方法放入 WorkerOptions 类内,作为旧环境的替代实现
public function withParallelism(int $value): self
{
    // 重建实例会再次执行构造函数内的业务检查
    return new self($this->queue, $value);
}

这份替代方案适用于本例两个属性都可通过构造参数完整表达的条件。真实类若有额外状态或工厂约定,应显式处理这些状态。不能把它当作所有对象通用的 clone polyfill。

选择旧环境实现时,应在发布包或支持分支中提供可被该环境解析的代码,不要只在旧解释器会加载的文件里放一个版本判断,就假设新的调用形式总能安全隐藏。CLI、Web运行环境与后台任务的 PHP 版本也可能不同,应按各入口实际环境部署对应实现。

处理常见错误与适用范围

应用调用更新方法时,可以按自己的错误处理约定接住非法业务值。下面片段接在完整示例之后使用,不构造成功结果来掩盖失败。

// 接在完整示例之后:非法业务值由受控更新入口拒绝
try {
    $candidate = $base->withParallelism(0);
} catch (InvalidArgumentException $e) {
    // 只演示错误边界;应用可按自身日志与响应约定处理
    echo $e->getMessage(), PHP_EOL;
}
现象优先理解的原因
外部覆盖 readonly 属性失败设置可见性仍生效,应使用类内受控方法
新值超出业务范围却没被拦住更新入口没有复用业务检查
更新方法调用后配置没变化调用者丢弃了返回的新对象
副本修改嵌套对象后影响原对象外层克隆默认是浅复制

clone with 会让 readonly 变成可随意写入吗?不会。它提供受规则约束的副本更新,原实例普通赋值仍受只读约束。

是否应把所有 wither 都改成 clone?不必。标量值对象较适合;构造过程承担大量业务约束、资源生命周期或派生状态的类,保留显式重建往往更直观。

属性名称可以直接来自请求参数吗?不建议。对外暴露具体业务方法,并明确允许更改的字段,比把任意数组直接交给克隆操作更容易保持约束。

PHP 8.5发布说明:https://www.php.net/releases/8_5_0.php

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