## **簡介**
Eloquent 是一個 ActiveRecord ORM 框架,ORM 全稱是 Object Relational Mapping,意為對象關系映射,用于實現面向對象編程語言里不同類型系統的數據之間的轉換,簡單來說,它會構建類與數據表之間的映射關系,從而建立起一個可在編程語言里使用的「虛擬對象數據庫」。「ActiveRecord」是 ORM 的一種實現模式,Eloquent 則是 Laravel 版的「ActiveRecord」。
一個 Eloquent 模型類映射一張數據表,通過模型類提供的方法,你可以獲取其映射的數據表的所有記錄,也可以獲取單條記錄,還可以創建、更新和刪除對應數據表記錄,而這一切都不需要你編寫任何 SQL 語句、或者構建查詢構建器即可完成。
## **模型類定義**
使用模型類之前,需要在數據庫有對應的數據表,因為模型類就是數據表在面向對象編程語言中的映射。比如我們前面幾篇教程中用到的`User`模型和`Post`模型都是這樣,創建一個模型類,需要使用`make:model`命令:
~~~
php artisan make:model Post
~~~
> 注:如果對應的數據表尚未創建,你還可以在創建模型類的同時創建對應的數據庫遷移文件,通過`php artisan make:model Post -m`即可。如果你想將模型類創建到`app/Models`目錄下,可以這么運行上述命令`php artisan make:model Models/Post`。
新生成的`Post`模型類里面什么東西都沒有,但是我們就可以通過它完成數據表記錄的增刪改查操作了,怎么做到的?這就是「約定優于配置」的功勞了。下面我們就來看看這些默認的約定。
- 表名:Eloquent 約定模型類映射表名是將類名由駝峰格式轉化為小寫+下劃線(含多個單詞的話),最后將其轉化為復數形式,比如`Post`對應表名是`posts`、`PostTag`對應表名是`post_tags`等等。當然,如果你不想遵循這個系統約定的規則,也可以通過手動設置模型類屬性的方式進行自定義:`protected $table= 'articles';`;
- 主鍵:Eloquent 默認假設每張數據表都有一個整型的自增主鍵,其字段名為`id`,如果你的數據表主鍵名不是`id`,可以通過`$primaryKey`屬性來指定——`protected $primaryKey = 'post_id';`;如果主鍵不是自增的,還可以設置`$incrementing`屬性為`false`——`public $incrementing = false;`;如果主鍵不是整型,還可以設置`$keyType`屬性為`string`——`protected $keyType = 'string';`;
- 時間戳:Eloquent 默認約定每張表都有`created_at`和`updated_at`字段(遷移類中`$table->timestamps()`會生成這兩個字段),并且在保存模型類時會自動維護這兩個字段。如果你的數據表里面不包含這兩個字段,或者只包含一個,都需要設置`$timestamps`屬性為`false`——`public $timstamps=false;`;或者通過`CREATED_AT`和`UPDATED_AT`常量來設置自定義的創建和更新時間字段:
~~~
public const CREATED_AT = 'create_time';
public const UPDATED_AT = 'update_time';
~~~
- 數據庫連接:Eloquent 模型類默認約定的數據庫連接是`config/database.php`中配置的默認連接,如果應用配置了多個數據庫連接,可以通過`$connection`屬性為模型類指定使用哪個連接:
~~~
protected $connection = 'connection_name';
~~~
## **查詢數據**
日常開發中,大部分操作都是數據庫中查詢數據,Eloquent 模型了為我們提供了很多方法幫助我們從數據庫中獲取數據。
### **獲取所有記錄**
我們可以通過模型類提供的`all`方法獲取一張表的所有記錄:
~~~
$posts = Post::all();
~~~
和查詢構建器一樣,該方法返回的也是集合,只不過是模型類集合:

如果結果集很大的話,類似于查詢構造器,模型類也支持通過`chunk`方法分塊獲取查詢結果::
~~~
Post::chunk(10, function ($posts) {
foreach ($posts as $post) {
if ($post->views == 0) {
continue;
} else {
dump($post->title . ':' . $post->views);
}
}
});
~~~
除此之外,在 Eloquent 模型中還可以通過`cursor`方法每次只獲取一條查詢結果,從而最大限度減少內存消耗:
~~~
foreach (Post::cursor() as $post) {
dump($post->title . ':' . $post->content);
}
~~~
### **獲取指定查詢結果**
如果想要指定查詢條件和查詢字段,可以通過`where`方法和`select`方法來實現:
~~~
$posts = Post::where('views', '>', 0)->select('id', 'title', 'content')->get();
~~~
實際上,Eloquent 模型類底層的查詢也是基于[查詢構建器]('')來實現的,你可以在模型類上調用所有查詢構建器的[Where 查詢方法](''),同樣是以流接口的模式構建方法鏈調用即可。前面提到的`chunk`和`cursor`方法也適用于這種指定查詢條件的查詢操作。
因為是查詢構建器,所以我們還可以在模型查詢操作中對查詢結果進行排序和分頁:
~~~
$posts = Post::where('views', '>', 0)->orderBy('id', 'desc')->offset(10)->limit(5)->get();
~~~
### **獲取單條記錄**
你也可以通過查詢構建器的方式在模型類查詢中獲取單條記錄:
~~~
$user = User::where('name', '學院君')->first();
//返回一個模型類實例
~~~
如果查詢的條件是主鍵 ID 的話,還可以將上述調用簡化為通過`find`方法來實現:
~~~
$user = User::find(1);
~~~
模型類查詢結果為空會返回`null`。如果你想要在單條記錄返回結果為空時返回 404 響應(在控制器方法中可能需要用到類似操作),可以通過`firstOrFail`或者`findOrFail`方法在找不到對應記錄時拋出 404 異常,從而簡化代碼編寫。
### **獲取聚合結果**
Eloquent 模型類同樣支持`count`、`sum`、`avg`、`max`、`min`等聚合函數查詢:
~~~
$num = User::whereNotNull('email_verified_at')->count(); # 計數
$sum = User::whereNotNull('email_verified_at')->sum('id'); # 求和
$avg = User::whereNotNull('email_verified_at')->avg('id'); # 平均值
$min = User::whereNotNull('email_verified_at')->min('id'); # 最小值
$max = User::whereNotNull('email_verified_at')->max('id'); # 最大值
~~~
你會發現,如果你掌握了查詢構建器,就等同于掌握了 Laravel 中的所有數據庫查詢操作。只不過將`DB::table`換成對應的模型類而已。
> 注:除獲取單條記錄之外,ELoquent 模型類查詢返回的結果都是集合類,因此你可以在查詢結果上調用集合類的所有方法,還可以自定義模型對應集合類,詳情請查看對應[官方文檔](https://xueyuanjun.com/post/9585.html)。
## **插入數據**
通過 Eloquent 模型類插入記錄到數據庫也比較簡單:
~~~
$post = new App\Post;
$post->title = '測試文章標題';
$post->content = '測試文章內容';
$post->user_id = 1;
$post->save();
~~~
創建時間和更新時間字段由 Eloquent 底層自動幫我們維護(遵循默認約定的話)。執行上面的代碼就會在數據庫新增一條記錄(我們在[Tinker]('')代碼):

此外,Eloquent 還為我們提供了一些快捷的插入方法,比如`firstOrCreate`和`firstOrNew`,這兩個方法都會先嘗試通過指定查詢條件在數據庫中查找對應記錄,如果沒有找到的話,會創建對應模型類的實例,并將查詢條件作為對應字段值設置到模型屬性上。
### **批量賦值**
回看上例,我們新增Eloquent模型時是通過依次設置每個屬性來實現的(`$post->title, $post->content.......`),如果模型類就那么三五個屬性還好,如果是十幾個甚至幾十個呢?到時候我們的控制器類里面可能會遍布這種設置代碼,Laravel 號稱的優雅就是打臉了。So,批量賦值就是為我們來解決這個問題的。
批量賦值允許我們以數組的方式將待設置屬性以關聯數組的方式傳遞構造函數:
~~~
$post = new Post([
'title' => '測試文章標題',
'content' => '測試文章內容'
]);
~~~
直觀來看,好像跟之前的寫法沒有什么大的優勢,依然需要指定每個屬性,但是這為我們提供了一個很好的基礎,如果和用戶請求數據結合起來使用,就能煥發它的光彩了。比如,如果我們的請求數據是一個文章發布表單提交過來的數據,包含`title`、`content`等字段信息,就可以通過下面這種方式進行批量賦值了:
~~~
$post = new Post($request->all());
~~~
這樣一來,不管多少字段,一條語句就搞定了全部屬性的賦值。但是,細心的同學可能會發現,這里有一個安全隱患,如果用戶發布的時候,包含了用戶字段`user_id`,并且設置的不是自己的用戶 ID,而是其它用戶的 ID,發布出來的文章就變成其他人發布的了;又或者文章需要審核后才能發布,但用戶在表單中傳遞了狀態字段將文章狀態設置為審核通過,這樣文章保存后就直接是已發布狀態了。諸如此類的問題還有很多,總而言之,批量賦值給我們帶來便利的同時,也給我們帶來了煩惱。
作為一個成熟的 ORM 框架,Eloquent 在設計之初肯定不會沒有考慮到這樣的問題,實際上,我們可以借助模型類中的白名單屬性或黑名單屬性來解決這個困擾。
所謂白名單屬性就是該屬性中指定的字段才能應用批量賦值,不在白名單中的屬性會被忽略;與之相對的,黑名單屬性指定的字段不會應用批量賦值,不在黑名單中的屬性則會應用批量賦值。可以看到,這兩個屬性是互斥的,只要設置一個屬性就可以解決所有問題了,不要同時設置兩個屬性。
Eloquent 模型類默認白名單屬性為空,黑名單屬性為`*`,即所有字段都不會應用批量賦值:
~~~
/**
* 使用批量賦值的屬性(白名單)
*
* @var array
*/
protected $fillable = [];
/**
* 不使用批量賦值的字段(黑名單)
*
* @var array
*/
protected $guarded = ['*'];
~~~
我們在實際開發中,對于頻繁變動的數據表,建議使用白名單,這樣安全性更好,因為哪些字段應用批量賦值始終是可控的,黑名單則會在后續新增字段的時候容易遺漏。而對于相對穩定或者字段很多的數據表,建議使用黑名單,免去設置字段之苦,但是對于這樣的模型類,每次修改數據表結構的時候都要記得維護這個黑名單,看看是否需要變動。
## **更新數據**
通過模型類更新數據表記錄也很簡單:
~~~
$post = Post::find(31);
$post->title = '測試文章標題更新';
$post->save();
//更新時間 Eloquent 底層會自動幫我們維護
~~~
同樣,Eloquent 也為我們提供了快捷的更新方法`updateOrCreate`,該方法首先會根據傳入參數對模型對應記錄進行更新,如果發現對應記錄不存在,則會將更新數據作為初始數據插入數據庫,并保存(不建議這么做,除非你的場景特別適合):
~~~
$user = user::updateOrCreate(
['name' => '學院君'],
['email' => 'admin@laravelacademy.org']
);
~~~
有的時候我們可能需要批量更新模型對應數據表的多條記錄,這可以借助查詢構建器來實現:
~~~
Post::where('views', '>', 0)->update(['views' => 100]
~~~
>對于更新模型類,也可以通過批量賦值的方式實現,只需在獲取模型類后使用`fill`方法批量填充屬性即可:
~~~
$post = Post::findOrFail(11);
$post->fill($request->all());
$post->save();
~~~
## **刪除數據**
通過模型類刪除對應數據表記錄和更新記錄類似,都要先獲取對應操作模型實例,刪除對應記錄更簡單,獲取到模型實例后,直接調用其刪除方法即可:
~~~
$post = Post::find(31);
$post->delete();
~~~
這樣,就完成了`id = 31`對應數據表記錄的刪除,你還可以通過 Eloquent 提供的`destroy`方法一次刪除多條記錄,通過數組傳遞多個主鍵 ID 即可:
~~~
Post::destroy([1,2,3]);
~~~
當然,你也可以通過查詢構建器的方式刪除指定記錄:
~~~
$user = User::where('name', '學院君')->fisrt();
$user->delete();
~~~
### **軟刪除**
在日常開發過程中,刪除數據庫記錄在所難免,但是我們多數時候并不想從數據庫中物理刪除記錄而只是想從業務角度邏輯刪除。這樣既保證了不出現在查詢結果中的實際需求,又滿足了統計或查看歷史數據的隱形需求。
Eloquent 模型類為我們提供了「軟刪除」功能的支持。在 Laravel 中,我們只需要在支持軟刪除的數據表中添加一個`deleted_at`字段——通過數據庫遷移來實現——就可以實現對數據庫記錄的「軟刪除」。比如我們想要讓`posts`表支持軟刪除,需要為其創建一個數據庫遷移:
~~~
php artisan make:migration alter_posts_add_deleted_at --table=posts
~~~
然后在新生成的遷移文件中編寫代碼如下:
~~~
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AlterPostsAddDeletedAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
}
~~~
這樣運行`php artisan migrate`命令即可在`posts`表中新增一個`deleted_at`字段,該字段默認值為NULL,表示沒有被軟刪除。如果要在模型類中支持軟刪除,需要在對應模型類(在本例中是`Post`模型)中添加支持軟刪除的 SoftDeletes`Trait:
~~~
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $guarded = ['user_id'];
}
~~~
> 注:你也可以修改這個默認約定的`deleted_at`字段,但何必費這個勁呢,除非你是從其它系統遷移過來的,原來的表結構已經存在了,這時候可以通過再模型類中設置靜態屬性`DELETED_AT`來自定義軟刪除字段。
要軟刪除一條記錄,在對應模型類實例上調用`delete`方法即可,底層會自動將數據表的`deleted_at`字段設置為當前時間——表示該記錄已經被「刪除」,而在模型類上做所有常規查詢操作的時候就會過濾掉被軟刪除的記錄。
如果是誤刪除的話,你可以`restore`方法來恢復軟刪除記錄:
~~~
$post->restore(); // 恢復單條記錄
Post::onlyTrashed()->where('views', 0)->restore();
// 通過onlyTrashed獲取被刪除記錄,通過restore可恢復多條記錄
~~~
如果你確實是想物理刪除數據表記錄,通過`forceDelete`方法刪除即可:
~~~
$post->forceDelete();
~~~
## **訪問器**
先來看一個例子,在帶有用戶功能的系統中,除了用戶注冊時使用的用戶名之外,我們有時候還允許用戶設置昵稱(可選的),當在頁面上顯示用戶名時,會優先展示用戶昵稱,如果該用戶沒有設置昵稱,則使用注冊時的用戶名。
對于這樣的需求,我們可以在每次獲取用戶信息后通過這段代碼進行設置:
~~~
if ($user->nickname) {
$user->display_name = $user->nickname; // 顯示用戶名為用戶昵稱
} else {
$user->display_name = $user->name; // 顯示用戶名為注冊時的用戶名
}
~~~
我們的系統中有大量顯示用戶名的地方,如果每次都這么設置,是件很讓人崩潰的事情,而且可維護性很差,萬一要修改用戶名顯示邏輯,每個地方都要修改。So,是時候祭出“訪問器”了。
訪問器用于從數據庫獲取對應字段值后進行一定處理滿足指定需求再返回給調用方。
要定義訪問器很簡單,在相應模型類中設置對應方法即可。以上面的`$user->display_name`為例,我們可以在`User`模型類中添加相應的方法`getDisplayNameAttribute`(注意這里的轉化方式,將小寫字母+短劃線格式屬性轉化為駝峰格式方法,后面的修改器也是這樣):
~~~
public function getDisplayNameAttribute(){
return $this->nickname ? $this->nickname : $this->name;
}
~~~
這樣,我們就可以在代碼中直接通過`$user->display_name`訪問期望的用戶名了,以后如果你想要修改用戶名顯示邏輯,直接改這個方法里的代碼就好了。
>注:訪問器方法名中包含的字段盡量不要和數據庫字段名同名,否則會覆蓋數據庫字段,導致通過模型屬性將永遠無法訪問該數據庫字段;另外,如果訪問器內部訪問了某個數據庫字段,則不能將訪問器和該數據庫字段同名,否則會導致循環引用而報錯。比如此例中,就不能將訪問器方法名設置為`getNameAttribute`或`getNickNameAttribute`。
## **修改器**
有了訪問器,相對的,就有修改器,修改器用于在字段值保存到數據庫之前進行一定處理滿足需求后再存到數據庫。比如做金融的同學可能比較熟悉,在保存用戶銀行卡號的時候需要加密后才能保存,顯示時需要對銀行卡號進行脫敏處理。
我們先定義一個加密銀行卡號的修改器(在此之前可以通過[數據庫遷移]('')為`users`表新增一個`card_no`字段):
~~~
public function setCardNoAttribute($value)
{
$value = str_replace(' ', '', $value); // 將所有空格去掉
$this->attributes['card_no'] = encrypt($value);
}
~~~
注意修改器傳入形參`$value`不能漏掉,否則無法正常設置屬性值。下面,我們通過模型類保存一個加密后的銀行卡號到數據庫:
~~~
$user = User::find(1);
$user->card_no = '6222020903001483077';
$user->save();
~~~
在數據回顯給用戶時,我們還要定義一個訪問器將加密數據解密并脫敏后顯示給用戶(脫敏【僅顯示后四位】是為了安全考慮,避免銀行卡號被爬取或劫持):
~~~
public function getCardNumAttribute()
{
if (!$this->card_no) {
return '';
}
$cardNo = decrypt($this->card_no);
$lastFour = mb_substr($cardNo, -4);
return '**** **** **** ' . $lastFour;
}
~~~
> 注:由于我們在訪問器內部訪問了`card_no`屬性,所以需要將訪問器方法名調整為`getCardNumAttribute`。
這樣,當我們查詢并獲取到對應模型實例后,訪問`$user->card_num`屬性,返回的就是脫敏后的銀行卡號了。
## **數組 & JSON轉化**
你有一定有過這種經歷,數據以 JSON 格式在數據庫中存取時,每次存儲時都要通過`json_encode`對數據進行編碼,讀取時都要通過`json_decode`對數據進行解碼。我們當然可以通過上述訪問器和修改器完成這種操作,但是 Laravel 提供了更加快捷的方法,對于一個在數據庫中類型為`JSON`或`TEXT`的字段,我們可以在模型類中將字段對應屬性類型轉化設置為數組,這樣在保存字段到數據庫時,會自動將數組數據轉化為 JSON 格式,在從數據庫讀取該字段時,會自動將 JSON 數據轉化為數組格式,方便操作。
同以`users`表為例,我們為其新增一個類型為`JSON`格式的字段`settings`,用于保存用戶設置信息(MySQL 5.7 以下版本設置字段類型為`TEXT`格式),然后在`users`表中設置`settings`類型轉化格式為`array`:
~~~
protected $casts = [
'settings' => 'array'
];
~~~
接下來,我們來測試下保存操作就能看到`settings`字段確實是以 JSON 格式保存到數據庫了:
~~~
$user = User::find(1);
$user->settings = ['city' => '杭州', 'hobby' => ['讀書','擼碼']];
$user->save();
~~~
## **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`門面記錄日志信息。
如果我們要監聽多個模型類的多個事件,像這樣編寫代碼的話,就會導致服務提供者的臃腫,而且不便于維護,所以 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
];
~~~
這樣當我們觸發`deleting`和`deleted`事件時,底層會將其轉化為觸發`UserDeleting`和`UserDeleted`事件。
最后,我們還要監聽上述自定義的事件類,我們可以通過在`EventServiceProvider`的`listen`屬性中為每個事件綁定對應的監聽器類,但通常情況下,我們會為某個模型類創建一個事件訂閱者類來統一處理該模型中的所有事件。在`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
];
~~~
### **通過觀察者監聽模型事件**
針對模型事件這種特殊的事件類型,Laravel還為我們提供了觀察者類來處理模型事件的監聽。觀測可以看作是上述訂閱者處理模型事件的簡化版本,我們不需要自定義事件類,不需要建立映射關系,只需要在觀察者類中將需要監聽的事件定義為同名方法,并在相應方法中編寫業務處理代碼即可。當某個模型事件觸發時,Eloquent 底層會去該模型上注冊的觀察者類中通過反射查找是否定義了對應的方法,如果定義了則執行相應的邏輯,否則忽略。
首先,我們通過 Artisan 命令初始化針對`User`模型的觀察者:
~~~
php artisan make:observer UserObserver --model=User
~~~
默認生成的`UserObserver`會為`created`、`updated`、`deleted`、`restored`、`forceDeleted`(強制刪除) 事件定義一個空方法。你可以把前面定義的`retrived`、`deleting`、`deleted`事件監聽代碼遷移過來,也可以將不需監聽的事件方法移除,這里我們將編寫保存模型時涉及的模型事件,包括`saving`、`creating`、`updating`、`updated`、`created`、`saved`:
~~~
<?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`模型上才能生效,我們可以在`EventServiceProvider`的`boot`方法中完成該工作:
~~~
public function boot()
{
parent::boot();
User::observe(UserObserver::class);
...
}
~~~
>注:我們已經介紹完了三種監聽 Eloquent 模型事件的方法和使用方式,如何選擇,視情況而定。如果只是監聽一兩個模型事件,第一種方式比較合適;如果僅僅監聽系統支持的模型事件,并且要監聽多個模型的多個事件,觀察者是最佳選擇;如果還要在模型類上監聽更多系統模型事件之外的自定義事件,則使用訂閱者來監聽比較合適。