ThinkPHP获取器与修改器是解决重复格式化的底层机制,需正确声明$type类型、配对编写、避免IO操作及空值异常,而非手动反复格式化。

为什么模型数据总要手动 format?
因为没用对 getAttr 和 setAttr —— ThinkPHP 的获取器与修改器不是“锦上添花”,而是解决重复格式化的底层机制。手动在控制器或模板里反复调用 date('Y-m-d', $time) 或 json_decode($json, true),本质是把模型当容器使,丢了它的职责。
常见错误现象:
• 控制器里一堆 foreach 循环做时间格式化
• 同一个字段在列表页、详情页、API 返回里写三遍格式逻辑
• 修改器没加 protected $type = ['xxx' => 'json'],导致 save() 时存成字符串却读不出数组
- 获取器(
getAttr)只在读取字段值时触发,比如$model->create_time或$model->toArray() - 修改器(
setAttr)只在赋值时触发,比如$model->data = $raw或$model->save(['data' => $raw]) - 二者不互相触发:设了
setDataAttr不代表读的时候自动getDataAttr,必须配对写
怎么写一个可靠的 datetime 获取器?
别直接在获取器里硬写 date('Y-m-d H:i:s', $value) —— 这会丢失原始时间戳,后续排序、比较、时区转换全崩。获取器的职责是“按需转义”,不是“永久覆盖”。
使用场景:
• 列表页显示“2024-05-20”
• API 接口返回 ISO8601 格式
• 后台导出需要精确到秒
立即学习“PHP免费学习笔记(深入)”;
- 用参数区分格式:
getCreateTimeAttr($value, $data)中检查$data['format'] ?? 'date',但更推荐拆成不同访问器,比如getCreateTimeDateAttr和getCreateTimeFullAttr - 优先返回
Carbon实例(如果项目已引入),而不是字符串 —— 它天然支持链式操作、时区切换、比较运算 - 注意兼容空值:
if (empty($value)) return '';,否则date()会警告
protected function getCreateTimeAttr($value)
{
if (empty($value)) return '';
return \Carbon\Carbon::createFromTimestamp((int)$value);
}
JSON 字段存取为什么总是出错?
核心问题不在获取器/修改器本身,而在类型声明和 NULL 处理。ThinkPHP 5.1+ 要求 JSON 字段必须显式声明 $type,否则 save() 会原样存字符串,toArray() 也不会自动 decode。
常见错误现象:
• 存的是 "{\"a\":1}"(字符串),读出来还是字符串
• 数据库字段为 NULL,获取器收到 null 却直接 json_decode(null) → 返回 null,前端报 undefined
• 修改器里用了 json_encode($value, JSON_UNESCAPED_UNICODE),但没处理资源、对象等非法类型
- 必须配
protected $type = ['config' => 'json'],这是开关,不是可选项 - 修改器里先
is_array($value) || is_object($value)再 encode,否则json_encode(123)会存成"123"(字符串) - 获取器里用
json_last_error() === JSON_ERROR_NONE判定是否合法,避免脏数据炸掉整个列表
protected function setConfigAttr($value)
{
if (is_array($value) || is_object($value)) {
return json_encode($value, JSON_UNESCAPED_UNICODE);
}
return $value;
}
protected function getConfigAttr($value)
{
if ($value === null || $value === '') return [];
$decoded = json_decode($value, true);
return json_last_error() === JSON_ERROR_NONE ? $decoded : [];
}
性能影响和缓存绕过风险
获取器和修改器每次取值/赋值都执行,不是惰性加载。如果里面做了 DB 查询、HTTP 请求、大数组遍历,性能立刻雪崩。
典型踩坑点:
• 在 getAvatarUrlAttr 里调用 User::find() 查头像配置
• 修改器里用 file_get_contents() 读本地文件生成缩略图路径
• 获取器返回结果没缓存,同一个模型实例反复调用 $user->name 触发多次处理
- 禁止在获取器/修改器里做 IO 操作;如需关联数据,请用关联模型或
with() - 复杂计算可加属性缓存:
private $formattedTime;+ 手动判空,但注意这会破坏模型序列化一致性 - 批量查询时,
toArray()会触发所有字段的获取器 —— 如果有 100 条记录、每条 5 个获取器,就是 500 次函数调用,比 SQL 还耗 CPU
最易被忽略的一点:获取器返回的对象(比如 Carbon 实例)在 toArray() 时不会自动转成字符串,除非你重写 toArray() 或给它加 __toString() 方法。否则前端拿到的是空对象或报错。



















