Laravel 模型事件允許你監聽模型生命周期內的多個關鍵點,甚至可以在阻止一個模型的保存或者刪除。 Laravel 模型事件文檔 概述了如何使用鈎子將對應事件與相關的事件類型關聯起來,但是本文的主旨是事件與監聽器的構建與設置,並額外補充一些細節的說明。
事件概述
Eloquent 有很多事件可以讓你使用鈎子將它們關聯起來,並且增加自定義的功能到你的模型中。該模型起始時有以下事件:
- retrieved
- creating
- created
- updating
- updated
- saving
- saved
- deleting
- deleted
- restoring
- restored
從文檔這里我們可以了解它們都是如何實現的,你還可以進入 Model
的基類去看看它們到底是如何實現的:
當現有模型被數據庫檢索時,
retrieved
事件將會觸發。當一個新的模型被第一次保存時,creating
和created
事件將會觸發。如果對一個已經存在於數據庫的模型調用save
方法,updating
/updated
事件將會觸發。無論怎樣,在這兩種情況下,saving
/saved
事件都會觸發。
文檔中對模型事件進行了很好的概述,同時解釋了怎樣使用鈎子去關聯事件,但是如果你是初學者,或者並不是熟悉怎樣使用鈎子將事件監聽器與這些自定義模型事件相關聯,請進一步閱讀本文。
注冊 事件
為了在你的模型中關聯一個事件,你需要做的第一件事是使用 $dispatchesEvents
屬性去注冊事件對象,這最終將通過 HasEvents::fireCustomModelEvent()
方法觸發, 該方法將通過 fireModelEvent()
方法被調用。 fireCustomModelEvent()
方法原始的時候大致是下面這樣:
/**
* 為給定的事件觸發一個自定義模型。
*
* @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
然后退出操作。比如,你可以使用這個鈎子去做一些檢測,也可以防止一個用戶被創建或刪除。
使用 App\User
模型舉例, 這里展示了如何配置你的模型事件:
protected $dispatchesEvents = [
'saving' => \App\Events\UserSaving::class,
];
你可以使用 artisan make:event
命令來為你創建這個事件, 但基本上這將是你最后得到結果 :
<?php
namespace App\Events;
use App\User;
use Illuminate\Queue\SerializesModels;
class UserSaving
{
use SerializesModels;
public $user;
/**
* 創建一個新的事件實例
*
* @param \App\User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
}
我們的事件提供了一個公有的 $user
屬性以便你能夠在 saving
事件期間訪問 User
模型實例。
為了讓它工作起來下一步需要做的是為這個事件建立一個實際的監聽器。我們設置好模型的觸發時機,當 User
模型觸發 saving
事件,監聽器就會被調。
創建一個事件監聽器
現在,我們定義 User
模型並注冊一個事件監聽器來監聽 saving
事件的觸發。雖然,我能通過模型觀察器快速實現,但是,我想引導你為單個事件觸發配置事件監聽器。
事件監聽器就像 Laravel 其它事件監聽一樣,handle()
方法將接收 App\Events\UserSaving
事件類的一個實例。
你可以手動創建它,也可以使用 php artisan make:listener
命令。 不管怎么樣,你都將創建一個像下面這樣子監聽類:
<?php
namespace App\Listeners;
use App\Events\UserSaving as UserSavingEvent;
class UserSaving
{
/**
* 處理事件。
*
* @param \App\Events\UserSavingEvent $event
* @return mixed
*/
public function handle(UserSavingEvent $event)
{
app('log')->info($event->user);
}
}
我只是添加了一個日志記錄調用,以便於檢查傳遞給監聽器的模型。為此,我們還需要在 EventServiceProvider::$listen
屬性中注冊監聽器:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* 應用的事件監聽器。
*
* @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: "Aiden Cremin",
email: "josie05@example.com",
updated_at: "2018-03-15 03:57:18",
created_at: "2018-03-15 03:57:18",
id: 2,
}
如果你已正確注冊了事件和監聽器,則應該在 laravel.log
文件中可以看到該模型的 JSON 表達形式:
[2018-03-15 03:57:18] local.INFO: {"name":"Aiden Cremin","email":"josie05@example.com"}
要注意的一點,此時模型並沒有 created_at
或 updated_at
屬性。如果在模型上再次調用 save()
,日志上將會有一個帶有時間戳的新記錄,因為 saving
事件會在新創建的記錄或現在有記錄上觸發:
>>> $u = factory(\App\User::class)->create();
=> App\User {#741
name: "Eloisa Hirthe",
email: "gottlieb.itzel@example.com",
updated_at: "2018-03-15 03:59:37",
created_at: "2018-03-15 03:59:37",
id: 3,
}
>>> $u->save();
=> true
>>>
停止一個保存操作
某些模型事件是允許你進行阻止操作的。舉個荒謬的例子,假設我們不允許任何一個用戶的模型保存其屬性 $user->name
的內容為 Paul
:
/**
* 處理事件。
*
* @param \App\Events\UserSaving $event
* @return mixed
*/
public function handle(UserSaving $event)
{
if (stripos($event->user->name, 'paul') !== false) {
return false;
}
}
在 Eloquent 的 Model::save()
方法中,會根據事件監聽的返回結果判斷是否進行停止保存操作:
public function save(array $options = [])
{
$query = $this->newQueryWithoutScopes();
// 如果 "saving" 事件返回 false ,我們將退出保存並返回
// false,表示保存失敗。這為服務監聽者提供了一個機會,
// 當驗證失敗或者出現其它任何情況,都可以取消保存操作。
if ($this->fireModelEvent('saving') === false) {
return false;
}
這個 save()
是個很好的例子,它告訴了你如何在模型生命周期中自定義事件,以及被動執行日志數據記錄或者任務調度。
使用觀察者
如果你正在監聽多個事件,那么你可能會發現使用觀察者類來按類型分組存放事件會更加方便。這里是一個例子 Eloquent 觀察者 :
<?php
namespace App\Observers;
use App\User;
class UserObserver
{
/**
* 監聽 User 創建事件。
*
* @param \App\User $user
* @return void
*/
public function created(User $user)
{
//
}
/**
* 監聽 User 刪除事件。
*
* @param \App\User $user
* @return void
*/
public function deleting(User $user)
{
//
}
}
你可以在服務提供者 AppServiceProvider
中的 boot()
方法里注冊觀察者。
/**
* 運行所有應用服務。
*
* @return void
*/
public function boot()
{
User::observe(UserObserver::class);
}
了解更多
我建議你閱讀 Laravel 事件文檔 去了解有關事件和監聽器在整個框架中如何工作。 Eloquent 事件文檔 對於可用事件以及如何使用觀察者是一個非常好的參考。最后,我建議瀏覽 Illuminate\Database\Eloquent\Model
類來查找 fireModelEvent()
方法調用的用法去了解事件如何與模型和 HasEvents trait 將這些事件聯系在一起。
現代化 PHP 知識日新月異,尤其是 PHP7 出來以后,歡迎加入 PHP / Laravel 知識社區 一起成長。