當一個新模型初次被儲存,將會觸發
creating
以及created
事件。如果一個模型已經存在於資料庫而且呼叫了save
方法,將會觸發updating
和updated
事件。然而,在這兩個狀況下,都將會觸發saving
和saved
事件。
Laravel 在資料更新的時候會觸發兩個事件:updated 和 saved。單從官方文件的說明,saved 看起來像是一個方便開發者統一管理新增/更新動作的事件,讓重複的程式碼可以不用被寫在兩個地方(created 和 updated),但是其實 saved 和 updated 的觸發時機是不一樣的。
直接來看一下 Laravel eloquent 的原始碼。
// 495 行
public function save(array $options = [])
{
...
if ($this->exists) {
$saved = $this->isDirty() ?
$this->performUpdate($query) : true;
}
...
if ($saved) {
$this->finishSave($options);
}
return $saved;
}// 552 行
protected function finishSave(array $options)
{
$this->fireModelEvent('saved', false);
$this->syncOriginal();
if (Arr::get($options, 'touch', true)) {
$this->touchOwners();
}
}// 569 行
protected function performUpdate(Builder $query)
{
...
$dirty = $this->getDirty();
if (count($dirty) > 0) {
$this->setKeysForSaveQuery($query)->update($dirty);
$this->fireModelEvent('updated', false);
}
return true;
}
應該盡量使用 updated 而不是 saved 來判斷資料是否更新
可以看到在觸發 updated 之前會先做一次 getDirty()
的檢查,其實就是在檢查這次更新的資料是不是真的有改動資料的值;相反的,saved 不管值有沒有改動都會觸發。如果沒注意到這個差別的話,這在實務上其實會造成一些問題。
例如,我們通常會把資料的結果快取起來,以減少對資料庫的查詢次數。因此當資料有變動的時候,就會需要把對應的快取刪除。為了避免有漏刪的部分,會把刪除快取的動作綁定在 eloquent 事件中。認清 updated 和 saved 之後,就會知道這個動作應該是要綁在 updated;若是綁在 saved 上,就會造成多餘的快取動作,增加機器的成本。當快取數量一多,又有用到類似 redis scan 這種較耗時的功能來找出需要刪除的快取時,對於機器的效能影響更大。
使用 updated 也仍會可能有漏網之魚
但是,要注意的是,即使使用了 updated 也不能說完全避免這件事的發生。這時候又要再看一次 Larvel eloquent 的原始碼了。
// 3093 行
public function getDirty()
{
$dirty = [];
foreach ($this->attributes as $key => $value) {
if (! array_key_exists($key, $this->original)) {
$dirty[$key] = $value;
} elseif ($value !== $this->original[$key] &&
! $this->originalIsNumericallyEquivalent($key)) {
$dirty[$key] = $value;
}
}
return $dirty;
}
可以發現 Laravel 判斷值有沒有改動的比較依據來自於,我們一開始 select 出來的欄位,跟這次要變動的欄位。意思就是,如果我們只 select 了 A 欄位出來,但是更新的是 B 欄位時,即使更動後 B 欄位的值跟更動之前一模一樣,Laravel 仍會把此視為資料的值有所改動。
不過這仍在可以接受的範圍之內。畢竟每個更新之前,若要為了拿到所有欄位而又再多查詢一次資料庫,可能會是一件更浪費資源的事情。