只读属性无法阻止已审核数据的二次修改,因其仅在模型save()时过滤字段,不拦截直接数据库update()操作,也不校验状态跳变合法性;真正防护需结合状态机方法封装、数据库约束及严格变更入口控制。

只读属性拦不住已审核数据的二次修改
$readonly 对 status=2(已审核)这类状态字段无效,它只在 save() 时丢弃传入的字段值,但不阻止你调用 Order::where('id', 123)->update(['status' => 3]) 直接改库;更不会校验“当前状态是否允许跳变”。比如从 status=2(已审核)直接改成 status=5(已作废),$readonly = ['status'] 完全不拦截——因为 update() 没走模型实例,压根没触发 readonly 过滤。
真正防二次修改得靠状态机封装
只读字段不是状态防护,而是字段屏蔽。防已审核数据被篡改,核心是控制“谁能在什么条件下改什么值”,必须用方法封装流转逻辑:
- 定义专用方法如
confirmDelivery()、revokeAudit(),内部先查当前status,再判断是否允许变更 - 禁止裸调
save()或update(),所有状态变更必须走这些方法 - 在方法里显式 throw 异常:
if ($this->status === 2) { throw new Exception('已审核订单不可撤销'); } - 数据库加 CHECK 约束(PostgreSQL)或触发器(MySQL),让非法 status 更新直接报错,不依赖 PHP 层
为什么把 status 加进 $readonly 反而坏事
设 $readonly = ['status'] 看似“锁住”,实则埋下三处隐患:
- 后台审核通过后,调用
$order->save()会跳过 status 字段,导致update_time更新了,但 status 还卡在 1(待审核)——业务逻辑断层 - 定时任务修复状态时用
Db::name('order')->where(...)->update(['status' => 2]),绕过模型,$readonly不生效,且没人知道它被悄悄覆盖了 - 前端提交带 status 的表单,后端因
$readonly忽略该字段,等于放弃校验入口,连“是不是合法客户端发的”都懒得问
最易忽略的静默失效点:时间戳和状态不同步
如果同时把 update_time 和 status 都放进 $readonly,那每次调用 save() 后,update_time 不更新、status 也不更新——界面显示“已保存”,日志写“操作成功”,但数据库里时间戳停在三天前,status 还是旧值。框架不报错、不警告、不补值,你只能靠人工比对才发现数据“冻住”了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











