__clone 仅在显式调用 clone 时触发,赋值、传参、json 序列化均不执行;它默认浅拷贝,深拷贝需手动克隆对象属性,且不兼容资源、闭包及部分内部类。

PHP 的 __clone 不是“改”出来的,而是必须显式用 clone 触发;不调用 clone,__clone 永远不会执行,哪怕你写得再完整。
__clone 只在 clone 时触发,赋值/传参/json_encode 都不走它
很多人以为对象赋值 $b = $a 或传参进函数会触发 __clone,结果发现改 $b->prop 连带影响 $a,其实是根本没走克隆流程。它只响应 clone $a 这一行代码。
常见错误现象:
-
Clone of non-object:试图clone null、资源(如fopen()返回值)或内部类(如SplFixedArray在 PHP 8.2+) -
Call to private __clone method:父类声明了private __clone(),子类重写也无效——访问性必须是public - JSON 序列化时改属性,误以为触发了
__clone,其实完全无关
深拷贝必须手动 clone 所有对象属性,不能靠递归自动处理
__clone 默认只做浅拷贝:标量、数组值复制一份,但对象、Closure、资源仍共享内存地址。所谓“副本一改,原对象跟着变”,八成是漏了 clone $this->nestedObj。
实操要点:
- 所有属性类型为对象的,都得显式
clone $this->prop,包括ArrayObject、DateTime等可克隆内置类 - 资源(如文件句柄、cURL 句柄)无法克隆,
__clone中应设为null或重新初始化 -
Closure直接clone会抛__clone method called on closure,得提前解构(如转成 callable 数组)或换用可序列化结构 - 嵌套层级深?别硬写递归
__clone,容易栈溢出或无限循环;优先考虑白名单式逐层控制
__clone 和 serialize/unserialize 行为完全独立
别指望 __clone 和反序列化效果一致。它们走的是两套机制:
-
unserialize()重建整个对象图,调用__wakeup,绕过__construct,受$allowed_classes控制 -
__clone只复制当前对象状态,不触发__wakeup,也不理__sleep的字段过滤逻辑 - 某些 ORM 实体(如 Doctrine Proxy)直接禁用克隆,需先判断
is_object($obj) && method_exists($obj, '__clone') - 性能上,
clone比unserialize(serialize($obj))快得多,但若对象持有 DB 连接等外部状态,clone不会自动断开,易引发并发问题
PHP 没有 clone with 语法,别被误导
PHP 8.5 并不存在,当前最新稳定版是 PHP 8.3;所谓 clone $obj with ['name' => 'new'] 是误传,混淆了 Kotlin/Rust 或已否决的 RFC 提案。
真实可行的替代方案:
- 在类中加
with(array $changes): self方法,用get_object_vars($this)+array_replace()合并后 new static() 构造新实例 - 注意:
get_object_vars()只返回 public 属性,私有/受保护成员需配合反射或显式白名单 - 该方式不调用
__clone,也不共享任何运行时资源(如 PDO 实例),和clone是两条路 - 如果既要克隆又要覆盖,只能分两步:
$copy = clone $obj; $copy->prop = $newVal;,但需警惕魔术方法(__set)、只读属性或类型约束副作用
最常被忽略的一点:__clone 不是深拷贝银弹。它适合明确知道结构、引用关系可控的场景(如 DTO、配置对象)。一旦对象含闭包、资源、代理、循环引用或第三方库实体,硬写 __clone 很容易漏掉边界条件,此时用构造器 + 显式参数传递反而更可靠。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











