定义事件

php artisan make:event TestEvent

生成的文件在 app\Events\TestEvent.php 内容如下

<?php

namespace App\Events;

use App\Models\Test;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TestEvent {
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $test;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Test $test) {
        $this->test = $test;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn() {
        return new PrivateChannel('channel-name');
    }
}

app\Listeners\TestEvent.php 文件内容

<?php
/**
 * Created by PhpStorm.
 * User: 舒孝元
 * Date: 2021/3/24 14:53
 * Email: sxy@shuxiaoyuan.com
 */

namespace App\Listeners;

use App\Events\TestEvent as TestEvents;

// 事件监听器队列,如果是耗时较长的任务,可以仍在队列里面执行,只需要 implements ShouldQueue,事件调度器会自动使用 Laravel 的队列系统自动排队
use Illuminate\Contracts\Queue\ShouldQueue;

// 如果你需要手动访问监听器下面队列任务的 delete 和 release 方法,你可以通过使用 Illuminate\Queue\InteractsWithQueue trait 来实现
use Illuminate\Queue\InteractsWithQueue;

class TestEvent implements ShouldQueue {

    use InteractsWithQueue;

    /**
     * 任务连接
     * 在config/queue.php配置中
     *
     * @var string|null
     */
    public $connection = 'redis';

    /**
     * 任务发送到的队列的名称
     * php artisan queue:work --queue=listeners
     *
     * @var string|null
     */
    public $queue = 'listeners';

    /**
     * 处理任务的延迟时间,单位:秒
     *
     * @var int
     */
    public $delay = 10;

    /**
     * 创建事件监听器
     *
     * TestEvent constructor.
     * @return void
     */
    public function __construct() {

    }

    /**
     * 处理事件逻辑
     *
     * @param TestEvents $event
     * @return void
     */
    public function handle(TestEvents $event) {
        $test = $event->test;
        $test->reserve_field1 = time() . ':这个是事件处理的,App\Events\TestEvent、App\Listeners\TestEvent';
        $test->reserve_field3 = time();
        $test->sex = time(); // 设置一个肯定失败的
        $test->save();
    }

    /**
     * 确定监听器是否应加入队列
     *
     * @param TestEvents $event
     * @return bool
     */
    public function shouldQueue(TestEvents $event) {
        // 只有 ID 小于等于 10 的才加入队列
        return $test = $event->test->id <= 10;
    }

    /**
     * 处理失败任务
     *
     * @param TestEvents $event
     * @param \Exception $exception
     * @return void
     */
    public function failed(TestEvents $event, $exception) {
        // 处理你的失败逻辑
        dd(123321);
    }

}

分发事件

event() 辅助函数可以全局使用,你可以在应用中的任何位置进行调用

private function test(Request $request) {
    $id = $request->input('id', 1);
    $test = Test::find($id);
    $test->openid = str_random(23);
    $test->reserve_field2 = time();
    $test->save();
    event(new TestEvent($test));
    return $test;
}

这是测试处理队列失败的情况

模型触发事件

允许你在模型生命周期中的多个时间点调用如下这些方法: 事件允许你在一个指定模型类每次保存或更新的时候执行代码

事件 说明
retrieved 获取到模型示例后触发
creating 插入到数据库前触发
created 插入到数据库后触发
updating 更新到数据库前触发
updated 更新到数据库后触发
saving 保存到数据库前触发(插入、更新之前,无论插入还是更新都会触发)
saved 保存到数据库后触发(插入、更新之后,无论插入还是更新都会触发)
deleting 从数据库删除记录前触发
deleted 从数据库删除记录后触发
restoring 恢复软删除记录前触发
restored 恢复软删除记录后触发

注:批量更新时不会触发相应事件,因为是直接走查询构建器完成的,绕过了模型方法

一、通过静态方法监听模型事件(不推荐)

直接在 app\Providers\EventServiceProvider.php 文件的 boot 方法中加入你需要监听的事件

//插入到数据库后的操作
public function boot() {
	parent::boot();
	//闭包函数传入参数是模型实例
	User::created(function ($user){
		Log::info('插入到数据库后实现的方法',$user->toArray());
	});
}

//插入到数据库前的操作,可以更改插入的值
public function boot() {
	parent::boot();
	User::creating(function ($user) {
		$user->password = 'shuxiaoyuan';
		Log::info('插入到数据库前实现的方法', $user->toArray());
	});
}

二、通过订阅者监听模型事件

创建相应事件类

创建一个 Test 表获取到模型示例后触发的事件:php artisan make:event TestRetrieved 文件路径:app\Events\TestRetrieved.php 代码如下:

<?php
/**
 * 事件订阅者,这里以 Test 模型事件为例
 * 需要在 Test 模型中建立模型事件与自定义事件类的映射
 *
 * User: 舒孝元
 * Date: 2021/3/24 20:43
 * Email: sxy@shuxiaoyuan.com
 */

namespace App\Events;

use App\Models\Test;

class TestRetrieved {
    public $test;

    public function __construct(Test $test) {
        $this->test = $test;
    }
}

在模型中建立映射关系

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Events\TestRetrieved;

class Test extends Model {
    protected $table = 'test1';
    protected $guarded = [];

    // 建立模型事件与自定义事件类的映射
    protected $dispatchesEvents = [
        'retrieved' => TestRetrieved::class,
    ];
}

创建订阅者监听事件类

可以直接在 app\Providers\EventServiceProvider.php 文件的 listen 属性中为每个事件绑定对应的监听器类,不过一般不建议这样做,我们在 App 目录下创建文件夹 Listeners 在文件夹中新建文件 TestEventSubscribe.php

代码如下:

<?php
/**
 * 事件订阅者,这里以 Test 模型事件为例
 *
 * User: 舒孝元
 * Date: 2021/3/24 20:16
 * Email: sxy@shuxiaoyuan.com
 */

namespace App\Listeners;

use Illuminate\Support\Facades\Log;
use App\Events\TestRetrieved;

class TestEventSubscribe {
    /**
     * retrieved:获取到模型实例后触发
     */
    public function onTestRetrieved($event) {
        // 处理你的逻辑,这里我们就简单写入一下日志
        Log::info('事件订阅者,获取到模型实例后触发:', ['info' => $event]);
    }

    /**
     * creating:插入到数据库前触发
     */
    public function onTestCreating($event) {
    }

    /**
     * 为订阅者注册监听器
     */
    public function subscribe($events) {
        $events->listen(TestRetrieved::class, TestEventSubscribe::class . '@onTestRetrieved');
//        $events->listen(TestCreating::class, TestEventSubscribe::class . '@onTestCreating');
    }
}

注册订阅者

app\Providers\EventServiceProvider.php 中注册订阅者

代码如下:

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider {
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot() {
        parent::boot();
    }

    /**
     * 确定是否应自动发现事件和侦听器。
     * 默认是禁止的,若开启,可以在部署时使用 event:cache 缓存所有事件
     * event:clear 清除缓存的事件
     *
     * @return bool
     */
    public function shouldDiscoverEvents() {
        return true;
    }

    /**
     * 获取应该用于发现事件的监听器目录
     * 默认是 Listeners 文件夹
     * @return array
     */
    protected function discoverEventsWithin() {
        return [
            $this->app->path('Listeners'),
        ];
    }

    /**
     * 注册订阅者
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\TestEventSubscribe',
    ];
}

到此设置完毕,我们去验证一下,随便搞个控制器方法去触发获取模型事件

/**
 * 测试模型事件订阅者
 *
 * @param Request $request
 */
private function subscribe(Request $request) {
    $id = $request->input('id', 1);
    $test = Test::find($id);

    return $test;
}

日志记录如下:

三、通过观察者监听模型事件

创建观察者

App 下新建目录 Observers,在该目录下新建观察者类 TestObserver 代码如下:

<?php
/**
 * 观察者监听模型事件
 *
 * User: 舒孝元
 * Date: 2021/3/25 11:54
 * Email: sxy@shuxiaoyuan.com
 */

namespace App\Observers;

use App\Models\Test;
use Illuminate\Support\Facades\Log;

class TestObserver {
    /**
     * 插入到数据库后触发
     *
     * @param Test $test
     */
    public function created(Test $test) {
        // 返回一个数据库不存在的字段
        $test->reserve_field4 = '这个字段是插入到数据库后给更新掉了';
        $test->save();
        $test->observe = '这是数据插入到数据库后触发的观察者模型事件';
    }

    /**
     * 插入到数据库前触发
     *
     * @param Test $test
     */
    public function creating(Test $test) {
        $test->reserve_field3 = '123';
    }
}

注册相应的观察者

app\Providers\EventServiceProvider.php 中注册订阅者

public function boot() {
    parent::boot();

    // 注册 Test 模型的观察者
    Test::observe(TestObserver::class);
}

到此设置完毕,我们去验证一下,随便搞个控制器方法去触发模型事件

/**
 * 观察者监听模型事件
 *
 * @param Request $request
 * @return mixed
 */
public function observe(Request $request) {
    $data = [
        'openid'         => str_random(23),
        'unionid'        => str_random(23),
        'nickname'       => '舒孝元',
        'mobile'         => '13311939117',
        'sex'            => 1,
        'reserve_field1' => '测试观察者监听模型事件',
        'reserve_field2' => '在入库前,将 123 写入 reserve_field3 表',
    ];

    $test_info = Test::create($data);
    return $test_info;
}

数据库中记录如下:

总结 : 如果只是监听一两个模型事件,通过静态方法监听方式比较合适;如果仅仅监听系统支持的模型事件,并且要监听多个模型的多个事件,观察者是最佳选择;如果还要在模型类上监听更多系统模型事件之外的自定义事件,则使用订阅者来监听比较合适。