Laravel观察者调度队列任务触发Invoice模型未找到异常
在Eloquent Observer中调度队列任务遇到的问题
错误信息
Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\Models\Invoice] in vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:621 #0 vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php(109): Illuminate\Database\Eloquent\Builder->firstOrFail()
观察者代码
public function created(OrderDeliveryOption $orderDeliveryOption): void { info('orderDeliveryOption created' . $orderDeliveryOption); if ($orderDeliveryOption->is_custom_label) { Log::info('Custom label created, tracking update needed', [ 'orderDeliveryOption' => $orderDeliveryOption ]); } UpdateOrderParcelLithiumStatusJob::dispatch($orderDeliveryOption->id) ->afterCommit() ->delay(now()->addSeconds(5)); }
上下文
- 该逻辑运行在
OrderDeliveryOption的created()观察者方法中。 - 任务为队列任务。
- 仅传递了
$orderDeliveryOption->id,而非完整模型实例。 - 队列处理时,Laravel抛出了针对
App\Models\Invoice的ModelNotFoundException。 - 堆栈跟踪指向
SerializesAndRestoresModelIdentifiers.php。
我的问题
- 为何仅传递整数ID,Laravel却尝试恢复
Invoice模型? - 观察者模型中加载的关联模型是否会导致序列化问题?
- 问题是否可能出现在任务的构造函数/属性/ trait中?
- 从观察者安全调度任务的最佳实践是什么?
补充说明
- 任务使用了
afterCommit() - 延迟时间为5秒
- 仅从观察者调用时才会出现错误
- 直接运行逻辑则可正常工作
问题解答
1. 为何仅传ID却尝试恢复Invoice模型?
虽然你传递的是整数ID,但如果UpdateOrderParcelLithiumStatusJob内部间接加载了关联模型(且该关联涉及Invoice),或者任务本身使用了SerializesModels/SerializesAndRestoresModelIdentifiers trait,同时任务的其他属性中包含已加载的Invoice模型实例,队列序列化/反序列化过程就会尝试恢复这个Invoice模型。比如任务执行逻辑里通过OrderDeliveryOption关联查询Invoice,但此时Invoice尚未创建或已被删除。
2. 观察者模型的关联是否会导致序列化问题?
会。如果OrderDeliveryOption模型通过全局作用域、$with属性预加载了Invoice关联,即使你只传ID,任务逻辑中查询OrderDeliveryOption时触发关联加载,而此时Invoice不存在,就会抛出异常。另外,日志中直接记录完整模型实例(比如代码里的info('orderDeliveryOption created' . $orderDeliveryOption);),序列化日志时可能连带序列化关联模型,虽一般不影响队列,但也可能埋下隐患。
3. 问题是否出在任务的构造函数/属性/trait中?
大概率是,重点检查:
- 任务是否使用
SerializesModels或SerializesAndRestoresModelIdentifierstrait:若任务其他属性包含模型实例(比如Invoice),序列化时会存储模型标识符,反序列化时会尝试查询,若不存在则抛出异常。 - 任务构造函数是否接收了除ID外的其他模型实例:比如不小心注入了Invoice或包含Invoice关联的模型。
- 任务执行逻辑中是否通过
OrderDeliveryOption查询关联的Invoice,但该Invoice在任务执行时还未创建(即使使用afterCommit(),仍可能存在事务外的操作延迟)。
4. 从观察者安全调度任务的最佳实践
- 仅传递标量值(ID、字符串等),避免传递完整模型实例,减少序列化风险。
- 强制使用
afterCommit():观察者的created/updated方法中,模型所在事务可能尚未提交,该方法能保证任务在事务提交后执行,避免查询未持久化数据。 - 任务内部查询模型时,用
find()替代findOrFail(),并处理模型不存在的情况,避免直接抛出异常导致任务失败。 - 避免在观察者中预加载不必要的关联,减少序列化负担和潜在问题。
- 日志记录仅保留必要字段(比如ID),而非完整模型实例,避免不必要的序列化。
需要检查任务类的内容
- 属性与构造函数:查看是否有除
$orderDeliveryOptionId外的其他模型属性(比如$invoice或包含Invoice的模型),确认这些属性的初始化逻辑,以及对应模型在任务执行时是否一定存在。 - 使用的Trait:若任务使用
SerializesModels或SerializesAndRestoresModelIdentifiers,检查所有属性是否可安全序列化,避免包含未持久化或可能被删除的模型实例。 - handle方法逻辑:查看是否通过
OrderDeliveryOption查询关联的Invoice(比如$orderDeliveryOption->invoice),需确认Invoice是否在OrderDeliveryOption创建后已正确创建,或添加判断逻辑(比如if ($orderDeliveryOption->invoice))避免调用不存在的关联。 - 模型查询逻辑:任务中查询
OrderDeliveryOption时,是否用了findOrFail()?改成find()并处理模型不存在的情况,避免因数据延迟导致的查询失败。
内容的提问来源于stack exchange,提问作者Nurul Komor
相关产品推荐
相关产品推荐

