thinkphp模型字段只读需配置静态$readonly数组,字段名须全小写且与数据库列名一致,仅对模型实例的save()和update()生效,静态调用或db类操作不触发该机制。

ThinkPHP 模型字段只读缓存怎么配 readonly 属性
模型字段设为只读,不是靠数据库层面限制,而是 ThinkPHP 在写入时跳过该字段——哪怕你传了值,它也不会进 SQL。关键在模型类的 $readonly 属性,必须是数组且字段名全小写。
-
$readonly是模型类的静态属性,不是数据库字段的AS别名或视图字段 - 字段名必须和数据库实际列名一致(比如
create_time,不能写成createTime) - 只对
save()、update()有效,insert()仍会尝试写入(除非配合$autoWrite或事件拦截) - 如果字段带前缀(如
user_id),也要按真实字段名写,不支持通配符或正则
为什么 readonly 字段在 update() 中还是被更新了
常见错因是没理解 ThinkPHP 的更新逻辑:它默认把所有传入数据当「待更新字段」,$readonly 只在模型层过滤,但如果你手动拼 SQL 或用了 Db::table()->update(),这个属性完全不生效。
- 必须用模型实例调用
update(),例如(new User())->update($data),不能直接用User::update($data)静态调用(静态方法绕过模型实例初始化) - 如果数据来自表单且含只读字段,建议先用
array_diff_key($data, array_flip($this->readonly))手动剔除,别依赖模型自动过滤 - 关联模型更新时,子模型的
$readonly不会自动继承,得各自定义
避免重复计算开销:用 getAttr + 缓存键控制字段惰性计算
真正要省开销的不是数据库字段,而是模型里那些带逻辑的获取器(getXXXAttr)。比如 getFullnameAttr 每次都拼接姓名,但其实结果可复用。这时候不能靠 $readonly,得自己加缓存键。
- 在获取器内部用
$this->getData('fullname_cache')先查缓存,有就直接返回 - 首次计算后,用
$this->setData('fullname_cache', $value)写入模型内存缓存(注意不是 Redis) - 不要在获取器里调用
$this->save()或查询数据库,否则引发递归或 N+1 - 如果字段依赖外部服务(如用户头像 URL),缓存键建议包含参数哈希,比如
'avatar_url_' . md5($this->id . $size)
缓存失效场景下,readonly 和获取器缓存怎么协同
字段只读和惰性计算缓存是两层事:$readonly 控制写入,获取器缓存控制读取。它们不自动联动,改了数据库值,模型不会自动清掉 getData() 里的缓存。
- 手动更新数据库后(比如后台 SQL 直接改了
status),记得调用$model->refresh()重载数据,否则getAttr还走旧缓存 - 批量操作(如
User::where(...)->update(...))后,模型实例已失效,不能再依赖其内部缓存 - 如果用 Redis 做全局字段缓存,key 设计必须包含模型版本号或更新时间戳,不然缓存永远不刷新
最常被忽略的是:$readonly 字段一旦被设为只读,连模型事件(如 before_update)里都拿不到它的新值——因为压根没进数据集。想干预只读字段逻辑,只能从请求层或事件前就处理。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











