订单创建成功后,系统通常还需要记录日志、发送通知或更新统计。如果把这些操作全部写在控制器中,代码会随着业务增加而变得难以维护。Laravel 的事件与监听器可以将这些后续操作拆开,让订单创建流程和各项处理逻辑分别维护。
以下示例采用 Laravel 12 的默认项目结构,以“订单创建后记录日志”为例,介绍事件定义、监听器创建、事件触发和队列化处理。
一、事件与监听器是什么
事件用于表达已经发生的业务动作,例如订单已创建、用户已注册。监听器负责在事件发生后执行具体操作。
两者的关系可以理解为:
创建订单
↓
触发 OrderCreated 事件
↓
执行监听器
├── 记录订单日志
├── 发送订单通知
└── 更新业务统计
一个事件可以对应多个监听器。以后增加新的后续处理逻辑时,可以添加监听器,减少对订单创建代码的修改。
事件默认不等于异步任务。 普通监听器通常在当前请求中执行;需要异步处理时,再将对应监听器交给队列。
二、创建订单事件
在项目根目录执行:
php artisan make:event OrderCreated
命令会创建:
app/Events/OrderCreated.php
将文件内容调整为:
<?php
namespace App\Events;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
class OrderCreated implements ShouldDispatchAfterCommit
{
use Dispatchable;
public function __construct(
public int $orderId
) {
}
}
这个事件只传递订单 ID,监听器可以根据实际需要查询订单。
ShouldDispatchAfterCommit 表示,如果事件在数据库事务中触发,Laravel 会等待事务成功提交后再派发事件。事务回滚时,对应事件不会派发;没有活动事务时,则正常派发。
订单创建、付款记录写入等场景适合采用这一方式,避免监听器处理尚未提交的数据。
三、创建日志监听器
执行:
php artisan make:listener WriteOrderCreatedLog --event=OrderCreated
打开生成的文件:
app/Listeners/WriteOrderCreatedLog.php
写入:
<?php
namespace App\Listeners;
use App\Events\OrderCreated;
use Illuminate\Support\Facades\Log;
class WriteOrderCreatedLog
{
public function handle(OrderCreated $event): void
{
Log::info('Order created', [
'order_id' => $event->orderId,
]);
}
}
监听器通过 handle() 方法接收事件对象,再读取其中的订单 ID。
在 Laravel 12 默认结构中,框架会扫描 app/Listeners 目录,并根据监听器方法中的事件类型声明发现对应关系。
执行以下命令查看注册结果:
php artisan event:list
输出中应能找到 OrderCreated 和 WriteOrderCreatedLog 的对应关系。
如果项目调整过事件发现目录或使用了自定义注册方式,应以实际配置为准。已经自动发现的监听器,不要再次手动注册,以免重复执行。
四、在订单创建后触发事件
假设项目已经存在 Order 模型,订单表包含:
user_id:下单用户 ID。status:订单状态。
在现有订单创建流程中,可以这样触发事件:
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
$order = DB::transaction(function () use ($userId) {
$order = new Order();
$order->user_id = $userId;
$order->status = 'pending';
$order->save();
OrderCreated::dispatch($order->id);
return $order;
});
其中,$userId 应来自当前经过身份验证的用户或已经完成权限检查的业务上下文。订单表如有其他必填字段,也需要一并赋值。
执行流程如下:
- 创建订单记录。
- 请求派发
OrderCreated事件。 - 数据库事务成功提交。
- Laravel 派发事件并调用日志监听器。
监听器产生的日志位置由项目日志通道决定。使用单文件日志通道时,可以查看:
tail -f storage/logs/laravel.log
正常情况下,日志中会出现 Order created 和对应的订单 ID。
五、为同一个事件增加监听器
假设还需要为新订单记录一条待处理日志,可以创建第二个监听器:
php artisan make:listener RecordPendingOrder --event=OrderCreated
示例内容如下:
<?php
namespace App\Listeners;
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Log;
class RecordPendingOrder
{
public function handle(OrderCreated $event): void
{
$order = Order::findOrFail($event->orderId);
Log::info('Pending order processing requested', [
'order_id' => $order->id,
'status' => $order->status,
]);
}
}
再次执行:
php artisan event:list
同一个事件下面应出现两个监听器。下一次触发事件时,两项处理都会执行。
这里用日志演示多监听器机制。实际项目可以将它替换为业务统计、站内通知等操作。
不要让多个监听器依赖彼此的执行顺序。 如果某个步骤必须等待另一步完成,应在明确的业务流程中组织调用,或者使用队列任务链。
六、将耗时监听器交给队列
发送邮件、调用外部接口等操作可能耗时较长,可以将监听器改为队列监听器。
以 RecordPendingOrder 为例:
<?php
namespace App\Listeners;
use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class RecordPendingOrder implements ShouldQueue
{
use InteractsWithQueue;
public int $tries = 3;
public function handle(OrderCreated $event): void
{
$order = Order::findOrFail($event->orderId);
Log::info('Pending order processing requested', [
'order_id' => $order->id,
'status' => $order->status,
]);
}
}
实现 ShouldQueue 后,Laravel 会将该监听器的执行工作交给配置的队列连接。
项目需要使用已经配置完成的异步队列,例如 database 或 redis。如果仍使用 sync 连接,监听器依然会在当前进程中执行。
启动工作进程:
php artisan queue:work
再次创建订单后,普通日志监听器会直接执行,队列监听器则由工作进程领取并处理。
队列任务可能重试。涉及发放积分、扣减库存、发送通知等业务时,需要额外设计幂等控制,避免重复执行产生重复结果。
七、验证监听器是否正常工作
先检查事件注册关系:
php artisan event:list
然后在测试环境中打开 Tinker:
php artisan tinker
查找一条已有订单并触发事件:
$order = App\Models\Order::query()->firstOrFail();
App\Events\OrderCreated::dispatch($order->id);
如果没有订单记录,应先通过正常业务流程创建测试订单。
接着检查:
- 普通监听器是否产生预期日志。
- 队列监听器是否进入队列并被工作进程处理。
- 是否存在重复日志或异常记录。
手动派发会再次触发所有相关监听器,因此应使用测试订单,避免对真实订单重复发送通知或执行其他业务操作。
新增监听器后,如果生产环境使用了事件缓存,可以重新生成:
php artisan event:cache
修改队列监听器代码后,常驻工作进程也需要加载新代码:
php artisan queue:restart
该命令会通知普通队列工作进程完成当前任务后退出,生产环境应由进程管理工具负责重新拉起。
八、常见问题
事件与队列任务有什么区别?
事件表达某个业务动作已经发生,便于多个监听器分别响应。队列任务表示一项等待执行的工作,主要解决异步处理、重试和后台执行问题。监听器可以使用队列,两种机制可以配合使用。
监听器抛出异常,会影响订单创建吗?
普通监听器的异常可能中断当前请求。本例在事务提交后派发事件,因此即使监听器随后报错,已提交的订单也不会自动回滚。对于需要重试的耗时操作,可以使用队列监听器,并做好失败处理。
使用事件后,还需要业务服务类吗?
需要时仍然可以使用。服务类负责组织创建订单等核心流程,事件负责通知后续处理,两者的职责不同。必须同步完成的关键业务步骤,不宜仅依赖松散的事件监听关系。

