Laravel Excel队列导出链式任务中捕获异常的正确方法
问题:Laravel Excel 导出为空时如何发送不同通知?
背景
我需要在导出文件存储完成后,向目标收件人发送邮件通知,该逻辑在Job类中实现:
public function handle(): void { (new ReportExport($this->report, $this->date))->queue($this->report->path) ->chain([ new NotifyReportRecipients($this->report), ]); }
导出类使用了FromGenerator特性:
public function generator(): Generator { $query = DB::query()->select(...)->from(...)->where(...); return $query->cursor()->getIterator(); }
当前逻辑已正常运行。
需求与问题
现在我希望在导出内容为空时发送不同的通知,尝试通过抛出异常在Job类中处理:
use Path\To\MyException; public function generator(): Generator { $query = DB::query()->select(...)->from(...)->where(...); throw_if($query->count() === 0, MyException::class); return $query->cursor()->getIterator(); }
但Laravel-excel的QueuedExport类未实现catch方法,调用时报错:
public function handle(): void { (new ReportExport($this->report, $this->date))->queue($this->report->path) ->chain([ new NotifyReportRecipients($this->report), ]) ->catch(function (MyException $e) { new NotifyReportRecipientsAboutEmptyReport($this->report), }); // 错误:Call to undefined method Maatwebsite\Excel\Jobs\QueueExport::catch() }
尝试使用try...catch块也无效:
public function handle(): void { try { (new ReportExport($this->report, $this->date))->queue($this->report->path) ->chain([ new NotifyReportRecipients($this->report), ]); } catch (MyException $e) { NotifyReportRecipientsAboutEmptyReport::dispatch($this->report); } }
按照Laravel Excel文档给导出类添加failed方法同样无效:
use Path\To\MyException; public function failed(Throwable $e) { if ($e instanceof MyException) { NotifyReportRecipientsAboutEmptyReport::dispatch($this->report); } } public function generator(): Generator { $query = DB::query()->select(...)->from(...)->where(...); throw_if($query->count() === 0, MyException::class); return $query->cursor()->getIterator(); }
更严重的是,无论导出是否为空,链式任务中的通知都会被分发。请问正确的处理方式是什么?
解决方案
1. 提前判断数据是否为空(推荐)
在触发导出的Job里先查询数据量,直接分支处理,避免进入队列后处理异常:
public function handle(): void { $query = DB::query()->select(...)->from(...)->where(...); if ($query->count() === 0) { NotifyReportRecipientsAboutEmptyReport::dispatch($this->report); return; } (new ReportExport($this->report, $this->date))->queue($this->report->path) ->chain([ new NotifyReportRecipients($this->report), ]); }
这种方式逻辑简单直接,不需要依赖队列异常机制,还能节省空数据场景下的队列任务资源。
2. 利用导出类生命周期钩子处理
在导出类中通过beforeExport钩子判断数据,再用afterExport触发对应通知,同时去掉原有的链式任务:
use Maatwebsite\Excel\Events\BeforeExport; use Illuminate\Contracts\Queue\ShouldQueue; class ReportExport implements FromGenerator, ShouldQueue { public $report; public $date; public $hasData = true; public function __construct($report, $date) { $this->report = $report; $this->date = $date; } public function registerEvents(): array { return [ BeforeExport::class => function (BeforeExport $event) { $query = DB::query()->select(...)->from(...)->where(...); if ($query->count() === 0) { $this->hasData = false; // 可以给空文件写入提示内容 $event->writer->getSheet()->getDelegate()->setCellValue('A1', '暂无数据'); } }, ]; } public function generator(): Generator { if (!$this->hasData) { yield; // 返回空生成器 } $query = DB::query()->select(...)->from(...)->where(...); return $query->cursor()->getIterator(); } public function afterExport() { if ($this->hasData) { NotifyReportRecipients::dispatch($this->report); } else { NotifyReportRecipientsAboutEmptyReport::dispatch($this->report); } } }
修改触发Job:
public function handle(): void { (new ReportExport($this->report, $this->date))->queue($this->report->path); }
这种方式把通知逻辑和导出流程绑定,符合Laravel Excel的生命周期设计。
3. 用统一Job统筹流程
创建一个单独的Job来管理整个导出+通知的流程,替代原有的链式任务:
class ProcessReportExport implements ShouldQueue { public $report; public $date; public function __construct($report, $date) { $this->report = $report; $this->date = $date; } public function handle(): void { $query = DB::query()->select(...)->from(...)->where(...); if ($query->count() === 0) { NotifyReportRecipientsAboutEmptyReport::dispatch($this->report); return; } // 同步执行导出(如果不需要队列导出可改用此方式) (new ReportExport($this->report, $this->date))->store($this->report->path); // 导出完成后发送正常通知 NotifyReportRecipients::dispatch($this->report); } }
调度这个Job即可:
ProcessReportExport::dispatch($report, $date);
这种方式把所有逻辑集中在一处,便于调试和维护。
内容的提问来源于stack exchange,提问作者IGP
相关产品推荐
相关产品推荐

