$readonly仅应用层过滤,无法拦截db原生操作、force强制赋值、字段名不匹配及关联模型修改;只读字段应设create_time、update_time、delete_time、source等,慎设user_id,勿设id。

$readonly 能拦住 save() 和 update(),但拦不住 Db::name()->update()、force 强制赋值、拼错字段名,也拦不住关联模型被改完再单独 save()。
只读字段该设哪些?别乱加
必须设只读的: create_time、update_time(除非你真要手动回滚时间)、delete_time(软删除标记)、source(来源标识,如 'wechat');谨慎设只读的:user_id 这类关联 ID —— 如果接口本就支持切换归属,加了反而卡住正常流程;别碰 id —— 主键在新增时自动忽略,加 $readonly 反而可能干扰自增或 UUID 生成。
$readonly 为什么没生效?检查这三处
常见失效不是框架坏了,而是写法绕过了它:
- 用了
Db::name('order')->update():原生 SQL 完全不走模型层,$readonly形同虚设 - 调了
$model->data($data, true)->save():第二个参数true表示强制赋值,会跳过$readonly过滤 - 字段名大小写或下划线不一致:数据库列是
pay_status,但$readonly写成payStatus或PayStatus,框架根本匹配不上
想真正防住,得组合用
$readonly 是应用层“礼貌性过滤”,不是锁死。真要防多入口写入(比如定时任务、DBA 直连、第三方客户端),得加一层:
- 数据库加默认值 +
GENERATED ALWAYS AS(MySQL 5.7+),让create_time根本无法被 INSERT/UPDATE 覆盖 - 验证器里用
only(['name', 'email'])明确放行字段,比$readonly更早拦截非法字段 - 敏感状态字段(如
status)别靠只读扛,封装专用方法changeStatus($new),内部做状态流转校验
关联模型的只读更难防
预载入 with('user') 后,$model->user->name = 'xxx' 再调 $model->user->save(),照样能改关联表 —— $readonly 在主模型里对关联模型无效。稳妥做法是:
- 在关联模型(如
User)的setAttr方法里拦截,比如protected function setNameAttr($value) { throw new Exception('name is readonly'); } - 或者直接避免修改预载入对象,改用
User::where('id', $model->user_id)->update(...)这类显式操作
最常被忽略的是:只读字段一旦设错,比如把本该动态更新的 update_time 加进 $readonly,那它就真不会自动更新了 —— 框架不会帮你补,也不会报错,数据就悄悄“冻住”了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











