You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

我的问题

  1. 为何仅传递整数ID,Laravel却尝试恢复Invoice模型?
  2. 观察者模型中加载的关联模型是否会导致序列化问题?
  3. 问题是否可能出现在任务的构造函数/属性/ trait中?
  4. 从观察者安全调度任务的最佳实践是什么?

补充说明

  • 任务使用了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或SerializesAndRestoresModelIdentifiers trait:若任务其他属性包含模型实例(比如Invoice),序列化时会存储模型标识符,反序列化时会尝试查询,若不存在则抛出异常。
  • 任务构造函数是否接收了除ID外的其他模型实例:比如不小心注入了Invoice或包含Invoice关联的模型。
  • 任务执行逻辑中是否通过OrderDeliveryOption查询关联的Invoice,但该Invoice在任务执行时还未创建(即使使用afterCommit(),仍可能存在事务外的操作延迟)。

4. 从观察者安全调度任务的最佳实践

  • 仅传递标量值(ID、字符串等),避免传递完整模型实例,减少序列化风险。
  • 强制使用afterCommit():观察者的created/updated方法中,模型所在事务可能尚未提交,该方法能保证任务在事务提交后执行,避免查询未持久化数据。
  • 任务内部查询模型时,用find()替代findOrFail(),并处理模型不存在的情况,避免直接抛出异常导致任务失败。
  • 避免在观察者中预加载不必要的关联,减少序列化负担和潜在问题。
  • 日志记录仅保留必要字段(比如ID),而非完整模型实例,避免不必要的序列化。

需要检查任务类的内容

  1. 属性与构造函数:查看是否有除$orderDeliveryOptionId外的其他模型属性(比如$invoice或包含Invoice的模型),确认这些属性的初始化逻辑,以及对应模型在任务执行时是否一定存在。
  2. 使用的Trait:若任务使用SerializesModels或SerializesAndRestoresModelIdentifiers,检查所有属性是否可安全序列化,避免包含未持久化或可能被删除的模型实例。
  3. handle方法逻辑:查看是否通过OrderDeliveryOption查询关联的Invoice(比如$orderDeliveryOption->invoice),需确认Invoice是否在OrderDeliveryOption创建后已正确创建,或添加判断逻辑(比如if ($orderDeliveryOption->invoice))避免调用不存在的关联。
  4. 模型查询逻辑:任务中查询OrderDeliveryOption时,是否用了findOrFail()?改成find()并处理模型不存在的情况,避免因数据延迟导致的查询失败。

内容的提问来源于stack exchange,提问作者Nurul Komor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 20:28:09