Laravel序列化模型无法加载关联关系问题排查
这个问题的核心原因是 SerializesModels trait 的工作机制:它在序列化 Job 中的模型时,只会保存模型的主键 ID,而非整个模型实例(包括已加载的关联数据)。当 Job 执行时,Laravel 会根据这个 ID 重新从数据库查询模型,但此时新查询出来的模型并没有预加载 country 关联,导致调用 $this->country->currency() 时,$this->country 是 null,进而触发报错。
而当你移除 SerializesModels 时,整个模型实例(包括已经加载的 country 关联数据)会被完整序列化,所以 Job 执行时可以直接使用之前加载好的关联,不会出现 null 的情况。
解决方案
这里有几种可行的解决方式,你可以根据业务场景选择:
1. 在 Job 的 handle 方法中主动预加载关联
这是最直接的方式,在处理逻辑前手动加载需要的关联:
public function handle() { // 预加载 country 关联 $this->customer->load('country'); doSomeStuff($this->customer->currency()); }
2. 在 Customer 模型中设置默认预加载关联
如果业务中大多数场景都需要访问 country 关联,可以在模型的 $with 属性中全局设置默认预加载:
class Customer extends Model { // 全局默认预加载 country 关联 protected $with = ['country']; public function country() { return $this->belongsTo(Country::class); } public function currency() { return $this->country->currency(); } }
3. 传递模型 ID 而非实例,手动查询并预加载
如果你不想依赖模型的自动加载逻辑,可以改为传递 Customer 的 ID,在 Job 中手动查询并指定预加载关联:
class SampleJob extends Job implements ShouldQueue { use InteractsWithQueue, SerializesModels, DispatchesJobs; private $customerId; public function __construct(int $customerId) { parent::__construct(); $this->customerId = $customerId; } public function handle() { // 查询时直接预加载 country 关联 $customer = Customer::with('country')->findOrFail($this->customerId); doSomeStuff($customer->currency()); } }
调用时改为:
dispatch(new SampleJob($customer->id));
为什么 Laravel 5.6 中正常?
Laravel 5.8 对 SerializesModels 的序列化逻辑做了优化,更严格地只保留模型主键;而在 5.6 版本中,序列化时可能会保留已加载的关联数据(或者当时的延迟加载逻辑不同),所以不会出现这个问题。但从性能和数据一致性角度来说,5.8 的设计更合理——毕竟 Job 执行时可能已经过了一段时间,数据库中的数据可能已经变化,重新查询模型能保证使用最新的数据。
内容的提问来源于stack exchange,提问作者Ivan

