自定义 Cast 类必须实现 Castable 接口或继承 CastsAttributes 抽象类,否则报错;$casts 中须用 ::class 语法;带参 Cast 需通过 Castable 的 castUsing() 实现;Cast 内应无状态、不访问模型关系或未初始化属性。

自定义 Cast 类必须实现 Castable 接口或继承 Cast
直接写一个普通类当 Cast 会报错:Class "App\Casts\CustomJson" is not a valid cast。Laravel 要求自定义 Cast 必须显式声明可被识别——要么实现 Castable 接口(返回真实 Cast 类),要么继承 Illuminate\Contracts\Database\Eloquent\CastsAttributes(即 Cast 抽象类)。
推荐用后者,更直观可控:
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class CustomJson implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return json_decode($value, true) ?: [];
}
public function set($model, string $key, $value, array $attributes): string
{
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
}
注意:set() 方法必须返回 string(或 null),否则 Eloquent 会静默丢弃该字段值,且不报错。
$casts 数组里写的是类名字符串,不是实例
在 Model 中配置时,$casts 的值是类的完整命名空间字符串,不是对象、不是 new 实例,也不是 class 关键字:
protected $casts = [
'metadata' => CustomJson::class,
'options' => 'array', // 内置 cast 可用字符串
];
常见错误包括:
- 写成
'metadata' => new CustomJson()→ Laravel 会尝试调用__toString(),报错 - 写成
'metadata' => CustomJson(缺::class)→ PHP 未定义常量 - 写成
'metadata' => 'App\Casts\CustomJson'→ 虽然能运行,但不推荐:硬编码字符串易出错,且 IDE 无法跳转
带参数的 Cast 需用 Castable 接口 + castUsing()
如果需要动态传参(比如指定 JSON 解码深度、是否关联数组),不能靠构造函数注入——Eloquent 实例化 Cast 时不传参。正确方式是让模型属性指向一个实现 Castable 的“代理类”:
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class JsonWithDepth implements Castable
{
public function __construct(public int $depth = 512) {}
public function castUsing(array $arguments): string
{
return JsonDepthCast::class;
}
}
// 然后在 Model 中:
protected $casts = [
'payload' => JsonWithDepth::class.':1024',
];
这里 ':1024' 会被作为 $arguments 传入 castUsing(),你可以解析并返回不同行为的 Cast 类。注意:冒号后内容是字符串,需手动 (int) 转换。
Cast 中访问模型状态要小心循环和延迟加载
在 get() 或 set() 里调用 $model->someRelation 很危险:可能触发无限递归(比如 relation 也用了同个 Cast)、或因模型尚未完全初始化导致属性为空。更糟的是,set() 执行时模型可能还没分配主键,save() 前无法可靠查库。
真正安全的做法是只处理当前字段原始值:
- 避免在 Cast 里调用
$model->getAttribute()或关系属性 - 如需上下文(如租户 ID、用户权限),应通过模型的静态属性、请求上下文(如
request()->user())或服务容器获取,而非依赖模型实例状态 - 若逻辑复杂到必须依赖其他字段,考虑改用 Accessor/Mutator,或在
saved事件中后置处理
Cast 的设计初衷是字段级无状态转换,一旦掺入业务逻辑,就容易在批量更新、队列任务、API 资源转换等场景下表现不一致。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











