读取器和修改器是数据进出模型时的自动过滤网,非语法糖;它们在模型实例取值或写入时按规则变形字段,但不参与导出、日志等脱离模型上下文的操作。

读取器和修改器不是语法糖,是数据进出模型时的自动过滤网。它们让字段在被读或被写时,按你定义的规则悄悄变形——比如查出来的时间戳变“2026-03-14”,存进去的密码自动 bcrypt 加密,状态码 1 显示成“正常”而不用每次在控制器里 switch。
什么时候该用读取器(Getter)?
当你反复在视图或 API 返回里对同一个字段做格式转换、映射或拼接,就该收进读取器了。它只在模型实例取值时触发,比如 $user->status、$user->toArray() 或 $user->toJson()。
- 常见错误:在控制器里手动
date('Y-m-d', $user['created_at'])—— 一旦字段名改了或逻辑变了,全项目得搜着修 - ThinkPHP 中命名必须是
getStatusAttr,Laravel 是getStatusAttribute,大小写和后缀错一个都不生效 - 读取器返回的是“展示值”,不影响原始数据;想拿原始值?ThinkPHP 用
$user->getData('status'),Laravel 用$user->getRawOriginal('status') - 别在读取器里做 DB 查询或 HTTP 请求——它可能被调用几十次(比如列表页渲染),会拖慢整页响应
修改器(Mutator)真正起作用的三个时刻
修改器不是“每次赋值都运行”,它只在模型层的数据写入流程中触发,比如 $user->password = '123'、$user->save()、User::create([...])。绕过模型直接执行 Db::table()->insert() 或原生 SQL,修改器完全不生效。
- 典型场景:密码加密、邮箱转小写、时间字符串转时间戳、JSON 字段预序列化
- Laravel 修改器函数名是
setPasswordAttribute,ThinkPHP 是setPasswordAttr;注意 Laravel 的password字段如果被加到$casts里(如'password' => 'encrypted'),会和修改器冲突,优先级需测试确认 - ThinkPHP 修改器在
$user->data([...], true)才触发,第二个参数true表示启用自动处理,漏掉就白写了 - 别在修改器里 throw 异常——Eloquent/ThinkPHP 通常会静默吞掉,导致数据莫名存不进库,debug 时很难定位
读取器和修改器共有的坑:类型与顺序
它们看起来独立,但实际和模型的类型转换($casts、$type)、访问器优先级、序列化行为深度耦合。一个字段同时配了读取器、$casts 和 JSON 转换,结果可能出人意料。
- ThinkPHP 中,
getCreatedAtAttr读取器 +'created_at' => 'datetime'类型转换,会导致时间被格式化两次——先转成 datetime 对象,再被读取器转成字符串,容易报错 - Laravel 中,如果字段在
$casts里声明为'json',又写了getMetaAttribute,那$model->meta拿到的是数组,但$model->getOriginal('meta')是 JSON 字符串——两边不一致,前端传参或对比时容易翻车 - 读取器和修改器函数参数名不能随意改:Laravel 必须叫
$value(或带$attributes),ThinkPHP 是$value,改了就不传参,函数形同虚设
最常被忽略的一点:它们只活在模型实例生命周期里。导出 Excel、生成 PDF、写入日志这些脱离模型上下文的操作,读取器和修改器一概不参与——别指望它们能“全局生效”。该手动处理的地方,还是得手动处理。










