__clone 方法仅在显式使用 clone 关键字时调用,不用于赋值、传参或序列化;它默认浅拷贝,需手动处理对象属性、资源和闭包;与序列化行为不同,不触发 __wakeup 或受 __sleep 控制。

__clone 方法什么时候会被调用
只有当你显式调用 clone 关键字时,__clone 才会触发——它不会在赋值、函数传参或 json_encode 时自动执行。很多人误以为“对象赋值就是深拷贝”,结果发现改副本也影响原对象,其实是根本没走 __clone。
常见错误现象:Clone of non-object(克隆了 null 或资源类型)、Call to private __clone method(类里声明了 private __clone() 但没删掉)。
- 必须配合
clone $obj使用,单独定义__clone没任何效果 - 如果父类有
private __clone(),子类即使重写也无法调用,得确保可访问性是public - PHP 8.2+ 对内部类(如
SplFixedArray)克隆行为更严格,部分会直接报Cannot clone object
深拷贝要手动处理引用属性
__clone 默认只做浅拷贝:对象属性里的标量和数组值会被复制,但对象、资源、闭包这些引用类型仍指向同一内存地址。你看到“副本改了,原对象也变了”,大概率是忘了克隆嵌套对象。
使用场景:需要独立副本的配置对象、DTO、带缓存属性的实体(比如 $this->cache 是 ArrayObject)。
立即学习“PHP免费学习笔记(深入)”;
- 所有对象类型的属性都得显式
clone $this->prop,否则仍是引用 - 资源(如
fopen返回的句柄)不能克隆,需在__clone中重新打开或置为null - 闭包无法克隆,会抛
__clone method called on closure,建议提前解构或改用可序列化结构
示例:
class Config {
public $data;
public $cache;
public function __clone() {
$this->data = clone $this->data; // 假设 $data 是另一个对象
$this->cache = null; // 资源/缓存不继承,重置
}
}
__clone 和序列化行为不一致
别指望 __clone 和 serialize/unserialize 效果一样。前者只复制当前对象状态,后者会重建整个对象图,且绕过构造函数;而 __clone 不触发 __wakeup,也不受 $allowed_classes 限制。
性能影响:克隆比反序列化快得多,尤其对大对象;但若依赖外部状态(如数据库连接),__clone 不会自动断开,容易引发并发问题。
-
unserialize()会调用__wakeup,clone完全不碰它 - 如果类里用了
__sleep控制序列化字段,__clone无视这个逻辑,所有属性都参与克隆 - 某些 ORM 实体(如 Doctrine Proxy)禁用克隆,直接抛异常,得先判断
is_object($obj) && method_exists($obj, '__clone')
替代方案比硬写 __clone 更可靠
不是所有对象都适合靠 __clone 实现深拷贝。比如含循环引用、动态生成属性、或依赖运行时上下文的对象,手写 __clone 很容易漏掉边角情况。
推荐优先考虑:json_decode(json_encode($obj), true)(仅限纯数据)、自定义 toCopy() 方法、或用 ReflectionClass 遍历属性做可控克隆。
-
json_encode/decode会丢掉方法、私有属性、资源、对象类型信息,适合 DTO 场景 - 用
ReflectionClass可跳过不可见属性,也能跳过静态/常量,但要注意 PHP 8.1+ 的只读属性(readonly)不能被反射修改 - 第三方库如
myclabs/deep-copy内部也是靠反射 + 白名单,但它能处理循环引用和特殊类型
真正难的不是写 __clone,而是判断哪些属性该克隆、哪些该重置、哪些根本不能碰——这得看具体业务逻辑,没法一劳永逸。








