Laravel事件与监听器

2026-09-10 29

订单创建成功后,系统通常还需要记录日志、发送通知或更新统计。如果把这些操作全部写在控制器中,代码会随着业务增加而变得难以维护。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

输出中应能找到 OrderCreatedWriteOrderCreatedLog 的对应关系。

如果项目调整过事件发现目录或使用了自定义注册方式,应以实际配置为准。已经自动发现的监听器,不要再次手动注册,以免重复执行。

四、在订单创建后触发事件

假设项目已经存在 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 应来自当前经过身份验证的用户或已经完成权限检查的业务上下文。订单表如有其他必填字段,也需要一并赋值。

执行流程如下:

  1. 创建订单记录。
  2. 请求派发 OrderCreated 事件。
  3. 数据库事务成功提交。
  4. 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 会将该监听器的执行工作交给配置的队列连接。

项目需要使用已经配置完成的异步队列,例如 databaseredis。如果仍使用 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

该命令会通知普通队列工作进程完成当前任务后退出,生产环境应由进程管理工具负责重新拉起。

八、常见问题

事件与队列任务有什么区别?

事件表达某个业务动作已经发生,便于多个监听器分别响应。队列任务表示一项等待执行的工作,主要解决异步处理、重试和后台执行问题。监听器可以使用队列,两种机制可以配合使用。

监听器抛出异常,会影响订单创建吗?

普通监听器的异常可能中断当前请求。本例在事务提交后派发事件,因此即使监听器随后报错,已提交的订单也不会自动回滚。对于需要重试的耗时操作,可以使用队列监听器,并做好失败处理。

使用事件后,还需要业务服务类吗?

需要时仍然可以使用。服务类负责组织创建订单等核心流程,事件负责通知后续处理,两者的职责不同。必须同步完成的关键业务步骤,不宜仅依赖松散的事件监听关系。

  • 广告合作

  • QQ群号:4114653

温馨提示:
1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。 2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。