应使用 $model->getattributes() 获取原始属性值,避免 toarray() 触发访问器、json 字段双重编码及类型漂移;对比时需对 json 字段标准化(如 json_encode(..., json_sort_keys))并手动递归比对,不可依赖 array_diff_assoc() 或 getchanges()。

直接用 toArray() 或 getAttributes() 做模型对比,大概率出错——它不处理访问器、JSON 字段类型漂移、空值语义差异,也不区分“字段没变”和“字段根本不存在”。真要可靠比对,得绕开框架自动逻辑,自己控制数据源和比较方式。
怎么安全获取两个模型的原始属性做比对
别依赖 toArray(),它会触发 accessor,把数据库原始值“美化”掉(比如把 0 变成 false,或给 URL 加前缀)。也别用 json_encode($model),JSON 字段会被双重编码。
-
$model->getAttributes()是唯一能拿到当前内存中所有字段原始值的方法,不含 accessor 干扰 - 新建模型(
$model->exists === false)时,getOriginal()返回空数组,不能当“旧值”用 - 若要对比“数据库里是什么”和“现在改成了什么”,必须确保模型是通过
find()、first()等从 DB 加载的,且中间没调过refresh()或手动改过$model->original
为什么 getChanges() 不能直接用于审计日志
getChanges() 看起来省事,但它只在 saving 或 updating 事件里有效,且返回的是“本次修改中被 set 过的字段”,不是“数据库真实变化”。批量更新、软删除、JSON 字段内容微调,都可能让它漏判或误报。
- 执行
$model->delete()后,getChanges()不包含deleted_at,但你要审计“谁删了这条记录”,就得靠getOriginal('deleted_at')和当前值比 - 对 JSON 字段如
meta,getOriginal('meta')返回字符串,getAttributes()['meta']是 PHP 数组,直接===比肯定不等——得先json_decode($model->getOriginal('meta'), true)再比 - 如果模型用了
$casts = ['status' => 'boolean'],getOriginal('status')是字符串'1',而getAttribute('status')是布尔true,类型不同,===就失效
如何手动写一个最小化 diff 工具函数
别信“一行 array_diff_assoc() 解决问题”的说法。它不递归、不处理 null/0/'' 类型歧义、不兼容 JSON 字段结构。真正可控的做法是白名单 + 显式键检查。
- 先定义要审计的字段白名单:
$auditFields = ['name', 'email', 'status', 'meta']; - 取原始和当前属性:
$original = $model->getOriginal(); $current = $model->getAttributes(); - 对每个字段,分别检查是否存在、是否相等:
isset($original[$key]) && isset($current[$key]) && $original[$key] === $current[$key] - JSON 字段单独处理:
$originalMeta = json_decode($original['meta'] ?? '[]', true); $currentMeta = $current['meta'] ?? [];,再用递归函数比对
复制模型做快照时,replicate() 的真实行为
replicate() 不是深拷贝,也不是备份数据库状态,它只是基于当前 $attributes 创建一个新实例,并重置 $exists = false、清空主键和时间戳。它不保留 $original、$changes、关联关系或 $appends。
- 适合场景:临时保存“当前内存值”用于后续比对,比如控制器里改完再存前做校验
- 不适合场景:替代数据库快照;它拿不到 DB 原始值,
$copy->getOriginal()是空的 - 如果模型有
mutator(如setEmailAttribute()),replicate()拿到的是已处理后的值,不是原始输入
最易被忽略的一点:JSON 字段在 MySQL 中存的是字符串,Laravel 读出来后自动 decode 成数组,但两次相同内容的 json_encode() 可能因 key 排序、空格、换行不同而生成不同字符串——所以审计日志里别直接存 raw JSON,应标准化(比如用 json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_SORT_KEYS))。否则连“有没有变”都判断不准。











