thinkphp模型中必须设为只读的字段包括create_time、update_time(除非回滚)、source、delete_time;谨慎设只读的是关联id如user_id;主键id无需设只读。

ThinkPHP 模型里哪些字段该设为只读
只读字段不是为了“防黑客”,而是防止业务逻辑里意外覆盖关键值,比如 create_time、status(某些状态机场景)、uid(由登录态决定,不该被 POST 覆盖)。设错字段会直接导致数据不一致,比如用户自己提交 status=1 就把待审核订单改成已发货。
- 必须设只读:创建时间、更新时间戳(除非明确要手动回滚)、来源标识(如
source='wechat')、软删除标记delete_time - 谨慎设只读:关联 ID(如
user_id),如果接口本就允许切换归属,设只读反而卡住正常流程 - 别碰
id:主键在 ThinkPHP 新增时自动忽略,无需手动加只读,加了反而影响自增或 UUID 生成
用 readonly 属性声明只读字段(TP6/TP8)
ThinkPHP 的只读控制靠模型类的 readonly 静态属性,它只对 save()、update() 生效,不影响 insert() 或原生 SQL。注意:它不校验请求参数,只是过滤掉字段——传了也白传。
-
readonly是数组,写字段名字符串,不带表前缀,比如['create_time', 'status'] - 只对当前模型实例生效,子类不会继承,需各自定义
- 如果用了
allowField(true),readonly仍会生效;但若用allowField(['name', 'email'])显式放行,且没包含只读字段,则只读字段本来就不会进数据,readonly就没机会触发
class OrderModel extends Model
{
protected $readonly = ['create_time', 'pay_status'];
}
save() 时只读字段被 POST 覆盖了?检查这三处
明明写了 readonly,但日志里还是看到 status 被改了——大概率不是框架失效,而是绕过了模型层。
- 用了
Db::name('order')->update(...):原生查询完全不走模型,readonly形同虚设 - 调用了
$model->data($data, true)->save():第二个参数true表示强制赋值,会跳过readonly过滤 - 字段名拼错或大小写不一致:数据库是
pay_status,但readonly写成payStatus,框架认不出来
更安全的只读:结合验证规则 + 数据库约束
readonly 是应用层“礼貌性拒绝”,真要防死,得靠组合拳。尤其当系统存在多语言客户端、定时任务或 DBA 直连场景时,单靠 PHP 层拦不住。
- 数据库加
GENERATED ALWAYS AS(MySQL 5.7+)或默认值 + 触发器,让create_time根本无法被 INSERT/UPDATE 覆盖 - 在验证器里加
rule('status', 'in:0,1,2')并配合only(['status'])明确指定允许修改的字段,比readonly更早拦截 - 敏感字段(如余额、积分)建议彻底移出用户可提交字段列表,由服务端根据动作推导,而不是“禁止修改”
真正容易被忽略的是:只读保护只在模型 save 流程中起作用,而很多项目用 Db::transaction 批量更新、用 Cursor 流式写入、甚至用队列异步处理——这些路径下,readonly 一概不生效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










