$casts 是 Laravel 模型类型转换最稳妥方式,自动处理读写时的整数、布尔、日期、JSON 等转换;访问器更灵活但需规范命名;getAttributeValue()/setAttribute() 可绕过二者获取原始值。

模型里用 $casts 做基础类型转换最稳妥
Laravel 模型的 $casts 属性是处理数据库字段到 PHP 类型映射的第一道防线,它在属性读取和写入时自动触发,不依赖手动调用。适合整数、布尔、日期、JSON 等常见类型。
常见错误是把字符串型数字(如 "1")当成原始值直接比较,结果 if ($user->is_active === true) 失败——因为没 cast,它其实是字符串 "1"。
-
$casts必须定义为protected $casts = [...],键是字段名,值是目标类型(如"int"、"boolean"、"datetime") - 布尔类型会把
0/1、"0"/"1"、"false"/"true"、null都转成对应布尔值,但注意:MySQL 的TINYINT(1)存2会被转成true(PHP 非零即真),不是严格语义判断 - 整数类型对空字符串
""或null会转成0,不是报错,这点容易误判数据缺失 - 如果字段在数据库里是
VARCHAR但存的是 JSON 字符串,用"json"cast 才能自动解码成数组/对象;用"array"本质也是"json",只是 Laravel 会额外调用json_encode/json_decode
protected $casts = [
'status' => 'int',
'is_published' => 'boolean',
'meta' => 'json',
];
需要自定义逻辑时,优先覆盖 get* 和 set* 访问器
当 $casts 不够用——比如要把数据库里的 "active"/"inactive" 字符串转成布尔,或把秒级时间戳转成 Carbon 实例——就得写访问器。它比 $casts 更灵活,也更容易调试。
容易踩的坑是命名不规范导致访问器失效:方法名必须是 get{Column}Attribute 或 set{Column}Attribute,且列名首字母大写,下划线要删掉(如字段 last_login_at → getLastLoginAtAttribute)。
- 访问器只影响读取(
get*)或写入(set*)单次操作,不改变底层属性值,也不参与批量赋值(fill()或构造函数) - 如果同时用了
$casts和访问器,访问器会覆盖$casts的行为(因为访问器更靠前执行) - 别在访问器里做 DB 查询或耗时操作,它会在每次访问属性时触发,容易引发 N+1
- 返回值类型要和预期一致:比如想让
$user->role_name返回字符串,就别在getRoleNameAttribute里返回数组
public function getIsActiveAttribute($value)
{
return in_array($value, ['active', '1', 1], true);
}
public function setUpdatedAtAttribute($value)
{
$this->attributes['updated_at'] = $value instanceof \DateTimeInterface
? $value->getTimestamp()
: (int) $value;
}
getAttributeValue() 和 setAttribute() 是绕过访问器的底层入口
当你需要跳过访问器逻辑直接读写原始数据库值(比如导出原始数据、做字段比对、迁移脚本),就得调用模型的 getAttributeValue() 和 setAttribute() 方法。它们不走访问器,也不受 $casts 影响,是真正的“裸值”通道。
典型误用是以为 $model->attribute 就是数据库原值,结果发现被 cast 或访问器改过了,导出 CSV 时布尔字段变成 1/0 而不是 "true"/"false"。
-
$model->getAttributeValue('status')返回数据库查出来的原始值(比如"1"字符串) -
$model->setAttribute('status', 'pending')直接塞进$model->attributes,不触发setStatusAttribute - 这两个方法不会触发
$casts,也不会格式化日期或 JSON,适合做“保真”操作 - 注意:它们对关系属性(如
posts)无效,只对数据库字段有效
批量赋值(fill() / 构造函数)和 $casts 的配合有隐式时机
用 new User($data) 或 $user->fill($data) 时,$casts 会立刻生效,但访问器不会。这意味着你传进去的 "is_admin" => "1" 会被转成布尔 true,但如果你写了 setIsAdminAttribute,它反而不会运行——因为 fill() 走的是属性批量赋值路径,不经过 setter。
这导致一个常见混乱:本地测试时访问器好使,上线后批量创建时逻辑不一致。
- 想确保所有写入都走访问器?别用
fill(),改用逐个赋值:$user->is_admin = "1" - 如果坚持用
fill(),就把复杂逻辑全挪到$casts或自定义 cast 类里(实现CastsAttributes接口) - 自定义 cast 类比访问器更适合复用,比如多个模型都要把状态码转成枚举实例,就写一个
StatusCast - 数据库字段类型和 cast 类型不匹配时(如字段是
TEXT却 cast 成int),Laravel 不报错,而是静默转成0,非常难排查


















