Eloquent自定义转换器(Laravel 7+)比get/setFooAttribute更推荐,因其统一可控、自动参与全链路序列化;需实现CastsAttributes接口,安全JSON数组转换器应兜底空值与解析失败。

为什么 getFooAttribute 和 setFooAttribute 不再推荐用
因为 Eloquent 的 $casts 已支持自定义转换器(Laravel 7+),它比访问器/修改器更统一、更可控,且自动参与序列化、API 响应、模型数组转换等全链路。手动写访问器容易漏掉反向逻辑(比如只处理取值不处理设值),也绕过 toArray() 和 jsonSerialize() 的标准化流程。
常见错误现象:toArray() 返回的仍是原始类型(如字符串),但前端期望是数组;或数据库存的是 JSON 字符串,却没在读取时自动解码。
- 自定义转换器必须实现
Illuminate\Contracts\Database\Eloquent\CastsAttributes接口 - 必须定义
get(从数据库值转 PHP 值)和set(从 PHP 值转数据库可存值)两个方法 - 返回值类型需与字段语义一致:比如
get应返回array,set应返回string或null - 不要在
get中抛异常——Eloquent 在序列化时会静默忽略失败,导致字段消失
怎么写一个安全的 JSON 数组转换器
适用于存取 JSON 类型字段(如 MySQL 5.7+ 的 JSON 列,或通用 TEXT 字段),目标是让模型属性始终表现为 PHP array,且空值、null、非 JSON 字符串都能兜底处理。
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class JsonArrayCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
if ($value === null || $value === '') {
return [];
}
if (is_array($value)) {
return $value;
}
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : [];
}
public function set($model, string $key, $value, array $attributes)
{
if ($value === null || $value === []) {
return null;
}
if (!is_array($value)) {
$value = [];
}
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
关键点:
立即学习“PHP免费学习笔记(深入)”;
- 不依赖
json_last_error()——json_decode失败直接返回null,避免后续is_array(null)导致意外空数组 -
set方法里强制归一化输入,防止传入对象、字符串甚至布尔值导致json_encode输出不符合预期 - 使用
JSON_UNESCAPED_UNICODE避免中文被编码成\uXXXX,提升可读性与调试友好度
如何在模型中注册并验证是否生效
在模型类中声明 $casts,键为字段名,值为转换器类完整命名空间路径:
protected $casts = [
'options' => \App\Casts\JsonArrayCast::class,
];
验证是否生效最直接的方式是:在 Tinker 中测试读写闭环:
- 执行
$model->options = ['theme' => 'dark', 'lang' => 'zh']; $model->save();→ 检查数据库该字段是否存为合法 JSON 字符串 - 执行
$fresh = Model::find($model->id); dd($fresh->options);→ 确认输出是array而非string - 执行
$fresh->toArray()→ 确认options键对应值是数组,不是 JSON 字符串
容易踩的坑:
- 忘记在
$casts中写::class后缀,写成字符串路径(如'App\Casts\JsonArrayCast')会导致 Laravel 找不到类 - 转换器类未放在自动加载路径下(如没运行
composer dump-autoload),Tinker 会报Class not found - 字段在数据库中是
NULL,但转换器get方法没处理null分支,结果返回null,而前端解析时崩掉
什么时候该用原生 cast 而不是自定义转换器
Laravel 内置 cast 已覆盖多数场景,优先用它们更轻量、更稳定:
- 布尔值:
'active' => 'boolean',比手写转换器更兼容'1'、'true'、'on'等各种伪布尔输入 - 日期时间:
'published_at' => 'datetime:Y-m-d',支持格式化输出,且自动处理时区和 Carbon 实例 - JSON 对象(非数组):
'meta' => 'object',底层已做容错,无需重复造轮子 - 枚举(PHP 8.1+):
'status' => \App\Enums\StatusEnum::class,配合原生 enum 更安全
只有当业务需要特殊序列化逻辑(如加密存储、带版本号的结构化解析、兼容旧数据格式)时,才值得投入成本写自定义转换器。否则,越简单越可靠。
真正难的不是写转换器,而是想清楚:这个字段在数据库里存什么、在内存里该是什么类型、前端要什么格式、出错时该怎么降级——这些边界没理清,代码写得再工整也会在某个深夜报错。



















