php变量快照本质是深拷贝,需用unserialize(serialize($var))处理数组/对象(排除资源、闭包),或重写__clone()实现对象深克隆;json_encode仅作数据清洗,会丢失精度、类型和结构。

PHP 变量快照的本质是深拷贝,不是赋值或序列化
PHP 中直接用 $backup = $original 得到的只是引用(对对象)或浅拷贝(对数组嵌套对象时),根本不是快照。所谓“快照”,意味着后续修改原变量不影响备份,且嵌套结构也完全隔离。真正可靠的只有 unserialize(serialize($var)) 或 clone(仅限对象),但二者行为、限制和坑完全不同。
-
serialize() + unserialize()能处理绝大多数类型(数组、对象、资源除外),但会丢失闭包、资源句柄、部分 SPL 对象(如DirectoryIterator),且性能较差; -
clone只适用于对象,且默认只做浅拷贝,必须手动实现__clone()才能递归复制内部对象; - 对纯数组(不含对象),
json_decode(json_encode($arr), true)是常见替代,但会丢精度(如 float 转字符串、键名强转)、不支持NaN/INF、无法处理循环引用。
用 serialize 和 unserialize 做通用快照的实操要点
这是最接近“通用变量快照”的方案,尤其适合配置数组、DTO 数据等无资源/闭包的场景。但必须注意几个硬性边界:
- 确保变量不含
resource类型,否则serialize()会失败并抛出Exception: Serialization of 'resource' is not allowed; - 对象需实现
Serializable接口或有可用的__sleep(),否则私有属性可能丢失; - 若变量含匿名函数(closure),
serialize()直接报错:Exception: Serialization of 'Closure' is not allowed; - 示例:
$data = ['user' => ['id' => 123, 'profile' => (object)['name' => 'Alice']]]; $backup = unserialize(serialize($data)); $data['user']['id'] = 999; var_dump($backup['user']['id']); // 还是 123
对象快照必须重写 __clone() 才算真正隔离
PHP 的 clone 默认只复制对象本身,内部引用的对象仍共享。比如一个类持有一个 DateTime 实例,不干预的话,克隆体和原对象修改的是同一个时间点。
- 必须在类中定义
__clone()方法,并显式clone所有需要隔离的属性; - 漏掉任一嵌套对象,快照就失效;
- 无法自动处理动态属性或魔术属性(如通过
__get计算的值); - 示例:
class User { public $name; public $created; public function __construct($name) { $this->name = $name; $this->created = new DateTime(); } public function __clone() { $this->created = clone $this->created; // 关键:否则两个对象共用同一 DateTime 实例 } }
别在生产环境用 json_encode 做快照,除非你清楚它删了什么
很多人图方便用 json_decode(json_encode($var), true),但它不是深拷贝替代品,而是数据清洗工具。它会在快照过程中静默丢弃大量信息:
立即学习“PHP免费学习笔记(深入)”;
- 所有非标量键(如浮点数键、对象键)被强制转成字符串,且顺序可能变;
-
float精度丢失(1.0000000000000002→"1"); -
null、NaN、INF全部变成null或触发警告; - 资源、资源句柄、
SimpleXML对象、Closure全部消失,不报错也不提示; - 如果变量里有
DateTime实例,json_encode()会调它的__toString(),结果是字符串,不再是DateTime对象。
快照不是格式转换。该用什么方法,取决于你变量里到底有什么——先 var_dump(gettype($var), is_object($var), is_array($var)) 看清结构,再选路。不然备份完以为安全,改着改着发现两边一起变了。











