$readonly不能保护hash字段,因其仅过滤写入数据而不校验值合法性,且易被手动赋值、原生查询或关联模型绕过;真正防护需数据库generated always as + 应用层重算校验 + 验证器显式拒绝三重保障。

$readonly 对 Hash 字段无效,它只过滤写入数据,不阻止字段被覆盖;真正防改需靠数据库 GENERATED ALWAYS AS + 应用层校验双保险。
为什么 $readonly 不能保护文件 Hash 字段
Hash 字段(如 file_hash)本质是业务计算值,不是数据库自增或时间戳那种天然“只读”类型。$readonly 只在模型 save() 和 update() 时从传入数据中剔除字段,但如果你在控制器里手动赋值:$model->file_hash = $_POST['file_hash'],再调用 save(),它仍会跳过该字段——看似“生效”,实则根本没拦住恶意提交。
- 用户 POST 一个伪造的
file_hash,$readonly确实让它进不了 SQL,但你也失去了校验机会,等于放行了非法输入 -
$readonly不校验值本身是否合法,只做“字段丢弃”,对 Hash 这种强校验字段来说,丢弃 ≠ 安全 - 如果后续用
Db::name('file')->update()批量修正状态,file_hash会被直接覆盖,$readonly完全不触发
真正防 Hash 被篡改的三步落地法
Hash 是数据指纹,目标不是“不让改”,而是“改了立刻能发现”。单靠 PHP 层过滤远远不够。
- 数据库层面用
GENERATED ALWAYS AS (SHA2(CONCAT(id, created_at, file_size), 256)) STORED(MySQL 5.7+),让file_hash成为不可写计算列,任何UPDATE尝试写入都会报错 - 应用层上传后必须调用
hash_file('sha256', $file->getRealPath())重算,并与数据库存值比对;不信任客户端传来的任何file_hash字段 - 在验证器里显式拒绝接收
file_hash:用only(['name', 'user_id', 'type']),确保该字段压根不会进入数据流,比$readonly拦截更早、更彻底
常见踩坑:Hash 字段设成 $readonly 反而埋雷
有人把 file_hash 加进 $readonly,以为就万事大吉。结果上线后发现两个问题:
- 定时任务修复旧文件时,用
Db::table('file')->where(...)->update(['file_hash' => $newHash]),绕过模型层,$readonly形同虚设,且没人知道 Hash 已被悄悄替换 - 前端上传组件错误地把 Hash 当作普通字段提交,后端因
$readonly忽略它,导致新上传文件没做服务端校验,存了个假 Hash - 关联模型里也用了
file_hash(比如Attachment关联File),但子模型没定义$readonly,继承失效,关联保存时意外覆盖
最易忽略的点:Hash 校验必须异步补漏
服务端重算 Hash 是阻塞操作,大文件可能超时。不能只靠上传时一次校验就认为“永远可信”。必须有后台命令定期执行:php think check:file-integrity,扫描 file_hash 字段为空、或与当前物理文件不一致的记录。这个机制和 $readonly 无关,但少了它,Hash 防护就是纸糊的。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











