$readonly仅在模型save()和update()中过滤字段,不作用于原生SQL、强制赋值或大小写不匹配场景;需配合验证器与数据库约束实现分层只读控制。

$readonly 不是数据库锁,它只在模型的 save() 和 update() 里过滤字段——传了也进不了 SQL。真要防死,得靠验证器 + 数据库约束组合。
只读字段该设哪些?别乱加 id
必须设只读的: create_time、update_time(除非明确要回滚)、source、delete_time;这些字段一旦写错,直接导致时间错乱或软删除失效。
谨慎设只读的:关联 ID 如 user_id,如果接口本就支持切换归属(比如客服代操作),加了 $readonly 反而卡住正常流程。
别碰 id:主键在新增时框架自动忽略写入,加 $readonly 不但没用,还可能干扰自增或 UUID 生成逻辑。
$readonly 生效的前提和常见失效场景
$readonly 是静态数组,只对当前模型类生效,子类不会继承,必须各自定义。
它只拦截 save() 和 update(),以下情况完全绕过:
• 直接用 Db::name('order')->update() 写原生 SQL
• 调用 $model->data($data, true)->save(),第二个参数 true 表示强制赋值,跳过所有过滤
• 字段名大小写或下划线/驼峰不一致,比如数据库是 pay_status,但写成 payStatus 或 PAY_STATUS
比 $readonly 更早、更稳的拦截点
应用层只读只是“礼貌性拒绝”,尤其当系统存在定时任务、DBA 直连、多语言客户端时,PHP 层拦不住。
更可靠的组合是:
• 验证器里用 only(['name', 'email']) 明确放行可改字段,比 $readonly 更早拦截非法参数
• 数据库层面加默认值或 GENERATED ALWAYS AS(MySQL 5.7+),让 create_time 根本无法被 INSERT/UPDATE 覆盖
• 对关键状态字段如 status,在验证规则里加 rule('status', 'in:0,1,2'),防止用户 POST status=999 突破业务状态机
立即学习“PHP免费学习笔记(深入)”;
真正容易被忽略的是:只读控制必须分层。模型层过滤、验证器校验、数据库约束三者缺一不可,单靠 $readonly 就上线,等于把门锁换成贴纸。



















