Laravel模型Cast访问属性触发set方法,如何阻止该行为?
问题
我在Product类中为price属性定义了Cast:
protected $casts = [ 'price' => MoneyDecimalCast::class . ':RUB,true', ];
在该Cast的set方法中加入了dd("Run");。在控制器中执行:
$test = Product::query()->get()->first(); dd($test->price);
此时不会显示“Run”消息,但修改为:
$test = Product::query()->get()->first(); dd($test->price, $test->price);
则会调用set方法并显示“Run”。另外,在资源中返回产品列表时,每次访问price都会触发set方法。请问如何阻止访问带Cast的属性时调用set方法?
补充测试用的TestCast代码:
class TestCast implements CastsAttributes { public function get($model, string $key, $value, array $attributes) { return new Product(); } public function set($model, string $key, $value, array $attributes) { dd('Run'); } }
解决方案
问题根源
Laravel模型会跟踪属性的「脏数据」状态:当Cast的get方法返回的值与数据库原始存储值的类型/内容不一致时,模型会判定该属性已被修改。后续再次访问该属性、序列化模型(比如返回资源响应)时,框架会自动触发set方法,尝试将修改后的值转换回数据库存储格式,为后续可能的保存操作做准备。
比如你提供的TestCast中,get方法返回了全新的Product实例,和原始数据库值的类型完全不同,模型直接标记该属性为脏数据,后续访问就会触发set。
解决办法
让
get返回值与原始值兼容
如果Cast的作用只是转换数据格式而非返回全新对象,调整get方法,让返回值的类型与数据库存储类型匹配(比如数据库存decimal就返回数值类型而非对象),避免模型判定属性为脏数据。在Cast中同步原始属性值
在get方法末尾调用$model->setRawOriginal($key, $value),强制将原始属性值同步为数据库存储的原始值,让模型认为属性没有被修改:public function get($model, string $key, $value, array $attributes) { // 你的转换逻辑 $convertedValue = new Product(); // 同步原始值,避免标记为脏数据 $model->setRawOriginal($key, $value); return $convertedValue; }访问属性后手动清除脏标记
在控制器或资源中,第一次访问属性后,调用$model->syncOriginalAttribute('price'),手动清除脏数据标记:$test = Product::query()->get()->first(); $price = $test->price; $test->syncOriginalAttribute('price'); dd($price, $test->price);直接获取原始值(绕过Cast)
如果不需要Cast转换后的结果,在资源或控制器中直接调用$model->getOriginal('price')获取数据库存储的原始值,完全绕过Cast的get和set方法。
内容的提问来源于stack exchange,提问作者symfony_fan

