阅读 302

巧用 Laravel 模型事件 - 博客 - PHPCasts

原文链接: phpcasts.org

file

Laravel模型事件允许你利用模型生命周期中的各个点,甚至可以防止发生保存或删除。 Laravel 模型事件文档 概述了如何使用事件类来引入这些事件,但本文旨在建立并补充关于设置事件和监听者的一些额外细节。

事件概览

Eloquent 有许多事件,你可以将其添加到模型中并添加自定义功能。到目前为止,模型支持的事件如下:

  • retrieved
  • creating
  • created
  • updating
  • updated
  • saving
  • saved
  • deleting
  • deleted
  • restoring
  • restored

从官方文档中可以看到它们的工作原理,并且你可以跳到基本的Model类中,看看它们是如何工作的:

当在数据库中一个模型 Model 被检索时,retrieved 事件将触发。 新模型第一次保存时,creatingcreated 事件将触发。 如果模型已经存在于数据库中并且调用了 save 方法,则 updating/ updated 事件将触发。 但是,在这两种情况下,saving / saved 事件都会触发。

该文档提供了一个很好的概述,并解释了如何触发这些事件,但如果你是新手或不熟悉如何将事件监听器连接到这些自定义模型事件,请进一步阅读。

注册事件

为了在模型中关联事件,首先需要使用 $dispatchesEvents 属性来注册事件对象,事件对象最终将通过 HasEvents::fireCustomModelEvent() 方法触发,该方法通过 fireModelEvent() 调用。fireCustomModelEvent() 方法如下所示:

/**
 * Fire a custom model event for the given event.
 *
 * @param  string  $event
 * @param  string  $method
 * @return mixed|null
 */
protected function fireCustomModelEvent($event, $method)
{
    if (! isset($this->dispatchesEvents[$event])) {
        return;
    }

    $result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));

    if (! is_null($result)) {
        return $result;
    }
}复制代码

某些事件(如 delete)将检查事件是否返回 false,然后退出操作。例如,你可以使用此hook进行一些检查并防止创建或删除用户。

App\User 模型为例,配置模型事件:

protected $dispatchesEvents = [
    'saving' => \App\Events\UserSaving::class,
];复制代码

你可以使用 php artisan make:event 命令来创建此事件,UserSaving 类的完整代码如下:

<?php

namespace App\Events;

use App\User;
use Illuminate\Queue\SerializesModels;

class UserSaving
{
    use SerializesModels;

    public $user;

    /**
     * Create a new event instance.
     *
     * @param \App\User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }
}复制代码

我们的事件类提供了一个公共 $user 属性,因此你可以在保存事件期间访问用户模型实例。

下面我们为事件设置一个实际的监听者。当用户模型触发 saving 事件时,监听者被调用,从而允许我们在事件期间使用模型。

创建一个事件监听器

现在我们的自定义 User 模型事件在保存期间触发,我们需要为它注册一个监听者。我们将在一秒钟内使用模型观察者,但我先来看下为单个事件配置事件(Event)和监听列表。

事件监听器就像任何其他Laravel事件监听器一样,handle() 方法将接受 App\Events\UserSaving 事件类的一个实例。

你可以手工创建它,或者使用 php artisan make:listener 命令。但是,你也可以这样创建它,下面是一个创建好的监听者类:

<?php

namespace App\Listeners;

use App\Events\UserSaving as UserSavingEvent;

class UserSaving
{
    /**
     * Handle the event.
     *
     * @param  \App\Events\UserSavingEvent $event
     * @return mixed
     */
    public function handle(UserSavingEvent $event)
    {
        app('log')->info($event->user);
    }
}复制代码

绑定事件和监听者

上面只是给 app('log')->info($event->user); 添加了一个log调用,以便现在可以检查传递给侦听器的模型。为此,我们需要在 EventServiceProvider::$listen 属性中注册监听器:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

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

    // ...
}复制代码

现在,当模型调度事件时,我们已注册的侦听器,就可以在 saving 事件期间被调用。

试用事件处理程序

我们可以使用 tinker 来快速尝试我们的事件监听器:

php artisan tinker
>>> factory(\App\User::class)->create();
=> App\User {#794
     name: "phpcasts",
     email: "model_event@phpcasts.org",
     updated_at: "2018-05-21 13:57:18",
     created_at: "2018-05-21 13:57:18",
     id: 2,
   }复制代码

如果你已正确注册了事件和侦听者,则应该在 storage/logs/laravel.log 文件中看到该模型的JSON表示形式:

[2018-05-17 13:57:18] local.INFO: {"name":"phpcasts","email":"model_event@phpcasts.org"}复制代码

请注意,此时模型没有 created_atupdated_at 属性。 如果你在模型上再次调用 save(),则日志将具有包含时间戳的新记录,因为 saving 事件会在新创建的记录和现有记录上触发:

>>> $u = factory(\App\User::class)->create();
=> App\User {#741
     name: "phpcasts",
     email: "model_event@phpcasts.org",
     updated_at: "2018-05-21 13:59:37",
     created_at: "2018-05-21 13:59:37",
     id: 3,
   }
>>> $u->save();
=> true
>>>复制代码

停止保存

一些模型事件允许你阻止继续操作。在我们这个测试的例子中,假设我们不希望在 $user->name 属性中保存包含 adminroot 等名称的用户的模型,则可以这样做:

/**
 * Handle the event.
 *
 * @param  \App\Events\UserSaving $event
 * @return mixed
 */
public function handle(UserSaving $event)
{
    if (in_array($event->user->name, ['admin', 'root'])) {
        return false;
    }
}复制代码

在基于 Eloquent Model::save() 的方法中,是否会根据此事件处理程序的结果停止保存的事件检查代码:

public function save(array $options = [])
{
    $query = $this->newQueryWithoutScopes();

    // If the "saving" event returns false we'll bail out of the save and return
    // false, indicating that the save failed. This provides a chance for any
    // listeners to cancel save operations if validations fail or whatever.
    if ($this->fireModelEvent('saving') === false) {
        return false;
    }复制代码

save() 方法对于自定义事件如何进入模型生命周期是个比较好的例子,并且可以被动地执行日志数据或派发作业等任务。

使用观察者

如果你正在监听多个事件,则可能会发现使用观察者类将一个类中的多个事件分组更加的方便。以下是来自 Eloquent事件文档 的一个例子:

<?php

namespace App\Observers;

use App\User;

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

    /**
     * Listen to the User deleting event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleting(User $user)
    {
        //
    }
}复制代码

可以在服务提供者 AppServiceProviderboot() 方法中注册观察者 :

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    User::observe(UserObserver::class);
}复制代码

总结

事件/监听者 和 观察者都可以达到监听模型事件的作用,体会两者的使用。

更多

可以阅读 Laravel事件文档,以更多地了解事件和监听者如何在整个框架中工作。 Eloquent事件文档 是可用事件以及如何使用观察者的很好参考。 最后,我建议通过浏览 Illuminate\Database\Eloquent\Model 类来查找fireModelEvent() 方法调用的用法,以查看事件如何与模型以及将这些事件组合在一起的 HasEvent trait。