$casts 比手动 mutator 更可靠,因其统一拦截所有读写路径,避免序列化、批量赋值等环节出错;需正确声明 protected、匹配字段类型,并注意 null 值不转换。

为什么 $casts 比手动 get*/set* 更可靠
直接在模型里写 getCreatedAtAttribute 或用 mutator 看似灵活,但会绕过 Eloquent 的序列化、批量赋值、JSON API 输出等环节,导致时间戳变成字符串、布尔字段被转成 "1" 或 "0"。而 $casts 是底层统一拦截点,所有读写路径(toArray()、json_encode()、fill()、create())都走同一套转换逻辑。
常见错误现象:dd($user->is_active) 显示 "1"(字符串),但预期是 true;或存入数据库的 json 字段在 PHP 里取出来还是字符串,没自动 json_decode。
-
$casts必须定义为protected $casts = [...],写在public或漏掉protected修饰符会导致失效 - 数据库字段类型要匹配:比如想用
'status' => 'boolean',字段实际是TINYINT(1)或BOOLEAN才能正确映射;若字段是VARCHAR存"true",Eloquent 不会识别 - 日期类转换(如
'published_at' => 'datetime')默认用模型的$dateFormat,不指定时依赖 MySQL 的Y-m-d H:i:s格式,自定义格式请改用'date:Y-m-d'
json 和 object cast 的区别与选型
当字段存的是 JSON 字符串(如 {"tags":["php","laravel"]}),用 'meta' => 'json' 会在读取时自动 json_decode(..., true) 成关联数组;而 'meta' => 'object' 会解成 stdClass 实例——这影响后续是否能用 array_key_exists 或 foreach 遍历。
容易踩的坑:'meta' => 'json' 在写入时如果传的是对象,Eloquent 会先 json_encode 再存,但若传的是已编码的字符串(如 '{"tags":[]}'),它不会二次 encode,也不会报错,结果存进去就是双重编码的字符串 "{\"tags\":[]}"。
立即学习“PHP免费学习笔记(深入)”;
- 推荐统一用
'json',除非业务强依赖对象属性访问(如$model->meta->tags) - 避免手动
json_encode后再赋值给 cast 字段,Eloquent 会处理好 - MySQL 5.7+ 的
JSON类型字段,PHP 层仍需靠$casts解码,类型本身不触发自动转换
自定义 cast 类解决复杂转换场景
内置 cast 处理不了的情况:比如数据库存的是逗号分隔字符串 "admin,user,guest",想在模型里暴露为数组;或存的是加密后的 base64 字符串,需要自动解密。
这时必须写一个实现 Illuminate\Contracts\Database\Eloquent\CastsAttributes 的类。核心是两个方法:get(从数据库值转 PHP 值)、set(从 PHP 值转数据库可存值)。
class CommaSeparatedArrayCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return $value ? explode(',', $value) : [];
}
public function set($model, string $key, $value, array $attributes)
{
return is_array($value) ? implode(',', $value) : '';
}
}
然后在模型里:protected $casts = ['roles' => CommaSeparatedArrayCast::class];
- 自定义 cast 类必须有无参构造函数,不能依赖容器注入(Eloquent 实例化时不走 DI)
- 不要在
get方法里做 DB 查询或 HTTP 请求,cast 是同步、无副作用的纯转换 - 如果转换逻辑涉及加密/解密,确保密钥不硬编码,可通过模型属性或 config 动态传入
时间字段用 datetime 还是 date?注意时区和存储精度
MySQL 的 DATETIME 和 TIMESTAMP 类型行为不同:TIMESTAMP 自动转时区(存 UTC,查本地时区),DATETIME 是原样存储。Eloquent 默认按 UTC 处理所有 datetime cast,但如果你的数据库字段是 TIMESTAMP 且未设时区,可能看到时间偏移 8 小时。
性能影响:用 'published_at' => 'date' 只取年月日,Eloquent 会把 2024-05-20 14:30:00 转成 Carbon::parse('2024-05-20'),但数据库仍存完整时间——只是读取后截断了。真正节省空间得靠数据库字段类型改为 DATE。
- 线上环境务必确认 MySQL 的
time_zone设置(SELECT @@time_zone;),和 Laravel 的config/app.php中'timezone'一致 -
'created_at' => 'immutable_datetime'可防止 Carbon 实例被意外修改,适合高并发场景 - 毫秒级精度需求(如 WebSocket 消息时间戳)需用
microtime(true)+ 自定义 cast,因为 MySQLDATETIME(3)和 Eloquent 默认不兼容毫秒
最常被忽略的是:$casts 对 null 值不做任何处理,字段为 NULL 时,无论声明什么 cast,返回的都是 null,不是空数组、空字符串或 false——这点必须在业务逻辑里单独判断。



















