进阶篇(六):Eloquent 模型事件和监听方式大全


所有支持的模型事件

在 Eloquent 模型类上进行查询、插入、更新、删除操作时,会触发相应的模型事件(关于事件我们后面会单独讲),不管你有没有监听它们。这些事件包括:

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

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

通过监听这些事件,我们可以在 Eloquent 模型实例生命周期的特定阶段执行特定操作。在 Laravel 中我们有多种方式来监听模型事件。

通过静态方法监听模型事件

一种是通过在模型类上调用要监听事件对应的静态方法,一般我们会在某个服务提供者的 boot 方法中完成这项工作,比如 EventServiceProvider。举个例子,假设我们要监听每次获取模型实例的事件并在日志中记录查询到的用户信息,可以这么做:

// app/Providers/EventServiceProvider.php

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

    // 监听模型获取事件
    User::retrieved(function ($user) {
        Log::info('从模型中获取用户[' . $user->id . ']:' . $user->name);
    });
}

上面这段代码中表示我们在 User 模型上监听 retrieved 事件,然后通过一个闭包函数执行对应的处理逻辑,该闭包函数传入参数是模型实例,在处理逻辑中,我们通过 Log 门面记录日志信息。

然后我们编写一段模型获取测试代码:

$user = User::find(1);
$users = User::whereBetween('id', [1, 5])->get();

执行这段代码,就可以在系统日志文件 storage/logs/laravel.log 中看到记录的日志信息了:

[2018-12-05 01:31:48] local.INFO: 从模型中获取用户[1]:学院君  
[2018-12-05 01:31:49] local.INFO: 从模型中获取用户[1]:学院君  
[2018-12-05 01:31:49] local.INFO: 从模型中获取用户[2]:Prof. Toni Collier  
[2018-12-05 01:31:49] local.INFO: 从模型中获取用户[3]:Prof. Lenny Abernathy V  
[2018-12-05 01:31:49] local.INFO: 从模型中获取用户[5]:Jaeden Oberbrunner DDS

很简单吧,监听其它模型事件也是一样的,改下监听事件名即可。

如果我们要监听多个模型类的多个事件,像这样编写代码的话,就会导致服务提供者的臃肿,而且不便于维护,所以 Eloquent 底层还支持我们以普通事件注册与监听的方式来监听模型事件。

通过订阅者监听模型事件

如果要通过自定义监听器监听模型事件,需要先创建对应的事件类,然后将 Eloquent 支持的模型事件与自定义的事件类建立映射关系,最后将事件类注册到监听器类中,从而完成模型事件监听闭环。

所以,我们先来创建自定义的事件类,这里我们以删除模型为例进行演示,分别定义一个删除前事件类和删除后事件类。我们通过 Artisan 命令来完成事件类初始化:

php artisan make:event UserDeleting
php artisan make:event UserDeleted

然后在这两个事件类中都添加 $user 属性并在构造函数中传入:

// app/Events/UserDeleted.php
// app/Events/UserDeleting.php

public $user;

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

接下来,我们要在 User 模型类中建立模型事件与自定义事件类的映射,这可以通过 $dispatchesEvents 属性来完成:

protected $dispatchesEvents = [
    'deleting' => UserDeleting::class,
    'deleted' => UserDeleted::class
];

这样,当我们触发 deletingdeleted 事件时,底层会将其转化为触发 UserDeletingUserDeleted 事件。

最后,我们还要监听上述自定义的事件类,我们可以通过在 EventServiceProviderlisten 属性中为每个事件绑定对应的监听器类,但是学院君更喜欢通过为某个模型类创建一个事件订阅者类来统一处理该模型中的所有事件。在 app/Listeners 目录下创建一个 UserEventSubscriber.php 文件作为订阅者类,编写代码如下:

<?php

namespace App\Listeners;

use App\Events\UserDeleted;
use App\Events\UserDeleting;
use Illuminate\Support\Facades\Log;

class UserEventSubscriber
{
    /**
     * 处理用户删除前事件
     */
    public function onUserDeleting($event) {
        Log::info('用户即将删除[' . $event->user->id . ']:' . $event->user->name);
    }

    /**
     * 处理用户删除后事件
     */
    public function onUserDeleted($event) {
        Log::info('用户已经删除[' . $event->user->id . ']:' . $event->user->name);
    }

    /**
     * 为订阅者注册监听器
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            UserDeleting::class,
            UserEventSubscriber::class . '@onUserDeleting'
        );

        $events->listen(
            UserDeleted::class,
            UserEventSubscriber::class . '@onUserDeleted'
        );
    }
}

最后,我们在 EventServiceProvider 中注册这个订阅者,使其生效:

// app/Providers/EventServiceProvider.php

protected $subscribe = [
    UserEventSubscriber::class
];

接下来,我们去编写一段删除代码:

$user = User::findOrFail(10);
$user->delete();

代码执行之后,就可以在日志文件中看到对应的日志记录了:

[2018-12-05 02:14:50] local.INFO: 从模型中获取用户[10]:Dr. Kory Abshire IV  
[2018-12-05 02:14:50] local.INFO: 用户即将删除[10]:Dr. Kory Abshire IV  
[2018-12-05 02:14:50] local.INFO: 用户已经删除[10]:Dr. Kory Abshire IV 

通过观察者监听模型事件

针对模型事件这种特殊的事件类型,Laravel 还为我们提供了观察者类来处理模型事件的监听。观察者可以看作是上述订阅者处理模型事件的简化版本,我们不需要自定义事件类,不需要建立映射关系,只需要在观察者类中将需要监听的事件定义为同名方法,并在相应方法中编写业务处理代码即可。当某个模型事件触发时,Eloquent 底层会去该模型上注册的观察者类中通过反射查找是否定义了对应的方法,如果定义了则执行相应的逻辑,否则忽略。

下面我们以 savingsaved 事件为例演示如何通过观察者监听模型事件。

首先,我们通过 Artisan 命令初始化针对 User 模型的观察者:

php artisan make:observer UserObserver --model=User

默认生成的 UserObserver 会为 createdupdateddeletedrestoredforceDeleted(强制删除) 事件定义一个空方法:

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * Handle the user "created" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * Handle the user "updated" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function updated(User $user)
    {
        //
    }

    /**
     * Handle the user "deleted" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleted(User $user)
    {
        //
    }

    /**
     * Handle the user "restored" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function restored(User $user)
    {
        //
    }

    /**
     * Handle the user "force deleted" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function forceDeleted(User $user)
    {
        //
    }
}

你可以把前面定义的 retriveddeletingdeleted 事件监听代码迁移过来,也可以将不需监听的事件方法移除,这里我们将编写保存模型时涉及的模型事件,包括 savingcreatingupdatingupdatedcreatedsaved

<?php

namespace App\Observers;

use App\User;
use Illuminate\Support\Facades\Log;

class UserObserver
{
    public function saving(User $user)
    {
        Log::info('即将保存用户到数据库[' . $user->id . ']' . $user->name);
    }

    public function creating(User $user)
    {
        Log::info('即将插入用户到数据库[' . $user->id . ']' . $user->name);
    }

    public function updating(User $user)
    {
        Log::info('即将更新用户到数据库[' . $user->id . ']' . $user->name);
    }

    public function updated(User $user)
    {
        Log::info('已经更新用户到数据库[' . $user->id . ']' . $user->name);
    }

    public function created(User $user)
    {
        Log::info('已经插入用户到数据库[' . $user->id . ']' . $user->name);
    }

    public function saved(User $user)
    {
        Log::info('已经保存用户到数据库[' . $user->id . ']' . $user->name);
    }
}

编写好观察者后,需要将其注册到 User 模型上才能生效,我们可以在 EventServiceProviderboot 方法中完成该工作:

public function boot()
{
    parent::boot();
    
    User::observe(UserObserver::class);
    
    ...
}

接下来,我们来测试观察者监听模型事件。我们先编写一段添加新用户的代码:

$user = new User();
$user->name = '学院君测试号01';
$user->email = 'test01@laravelacademy.org';
$user->password = bcrypt('secret');
$user->save();

执行上述代码后,即可在日志文件中看到相应的日志记录:

[2018-12-05 02:50:00] local.INFO: 即将保存用户到数据库[]学院君测试号01  
[2018-12-05 02:50:00] local.INFO: 即将插入用户到数据库[]学院君测试号01  
[2018-12-05 02:50:00] local.INFO: 已经插入用户到数据库[19]学院君测试号01  
[2018-12-05 02:50:00] local.INFO: 已经保存用户到数据库[19]学院君测试号01  

同理,编写一段更新已存在用户的代码:

$user = User::findOrFail(15);
$user->name = '学院君测试号15';
$user->save();

执行上述代码,对应的日志记录如下:

[2018-12-05 02:53:38] local.INFO: 从模型中获取用户[15]:Miss Lucile Langosh V  
[2018-12-05 02:53:38] local.INFO: 即将保存用户到数据库[15]学院君测试号15  
[2018-12-05 02:53:38] local.INFO: 即将更新用户到数据库[15]学院君测试号15  
[2018-12-05 02:53:38] local.INFO: 已经更新用户到数据库[15]学院君测试号15  
[2018-12-05 02:53:38] local.INFO: 已经保存用户到数据库[15]学院君测试号15

结语

至此,我们已经介绍完了三种监听 Eloquent 模型事件的方法和使用方式,如何选择,视情况而定。如果只是监听一两个模型事件,第一种方式比较合适;如果仅仅监听系统支持的模型事件,并且要监听多个模型的多个事件,观察者是最佳选择;如果还要在模型类上监听更多系统模型事件之外的自定义事件,则使用订阅者来监听比较合适。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 进阶篇(五):在 Eloquent 模型类上使用全局作用域和局部作用域进行查询

>> 下一篇: 进阶篇(七):Eloquent 模型关联关系(上)