在 Laravel 的世界中,你對 Eloquent 大多數操作都會或多或少的觸發一些模型事件,今天就來看一下模型事件的使用。
Laravel 事先已經定義好了 10 個模型事件以供我們使用,它們分別是:
creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored。
事件名稱都很淺顯易懂,如果你是認真寫代碼話都應該可以看明白,如果不明白的話可以左轉百度。
不過你可能對 updating, updated, saving, saved 這四個事件有所疑惑,所以我們來詳細分析一下。
打開 Illuminate\Database\Eloquent\Model ,找到位于 517 行的 save 方法
筆者使用的是 Laravel 5.5 最新版本,如果你使用的是其他版本,請自行在官方文檔 版本差異 中對比你的版本
~~~
public function save(array $options = [])
{
$query = $this->newQueryWithoutScopes();
if ($this->fireModelEvent('saving') === false) {
return false;
}
if ($this->exists) {
$saved = $this->isDirty() ?
$this->performUpdate($query) : true;
}
else {
$saved = $this->performInsert($query);
if (! $this->getConnectionName() &&
$connection = $query->getConnection()) {
$this->setConnection($connection->getName());
}
}
if ($saved) {
$this->finishSave($options);
}
return $saved;
}
~~~
可以看到首先觸發的是 saving:
~~~
$this->fireModelEvent('saving')
~~~
接著會判斷此 model 是不是新創建的。
如果是新創建的,那么則會進行 insert 操作,由于本例是分析修改操作,所以這里直接略過。
如果不是新創建的,那么會調用 isDirty 方法判斷此 model 是否進行了修改。
如果進行了修改,那么會調用 performUpdate 方法進行更新操作,如果沒有進行修改,則直接返回 true。
讓我們點開 performUpdate 方法。
~~~
protected function performUpdate(Builder $query)
{
if ($this->fireModelEvent('updating') === false) {
return false;
}
if ($this->usesTimestamps()) {
$this->updateTimestamps();
}
$dirty = $this->getDirty();
if (count($dirty) > 0) {
$this->setKeysForSaveQuery($query)->update($dirty);
$this->fireModelEvent('updated', false);
$this->syncChanges();
}
return true;
}
~~~
可以看到這里會觸發 updating:
~~~
$this->fireModelEvent('updating')
~~~
如果返回的不是 false ,那么會接著往下走,判斷一下此模型是否使用了 Timestamp,如果使用了,那么則先更新自身的 Timestamp:
~~~
if ($this->usesTimestamps()) {
$this->updateTimestamps();
}
~~~
接著調用 getDirty,顧名思義,大概可以知道這個方法是獲取自身的臟值,讓我們點開看一下:
~~~
public function getDirty()
{
$dirty = [];
foreach ($this->getAttributes() as $key => $value) {
if (! $this->originalIsEquivalent($key, $value)) {
$dirty[$key] = $value;
}
}
return $dirty;
}
~~~
意料之中,此方法內會返回自身被修改后的臟值,如果你打印過 Laravel 的 Model 實例,那么相信你會看到實例中有 original 和 attributes 兩個數組。
其中 original 保存的是最初始的實例屬性,無法被外部直接修改。
attributes 數組則是可以被自有修改,此方法即是由判斷兩個數組的差異而返回臟值,由于時間原因,這里不做過多詳解,有機會單開一個文章慢慢講。
繼續往下走,可以看到這里會判斷一下是否有臟值:
~~~
if (count($dirty) > 0)
~~~
只有在存在臟值的情況下才會進入 if 內部,執行 update 操作,繼而觸發 updated 事件
在 update 結束后會執行一下 syncChanges 同步一下自身的數據
OK, performUpdate 方法解析完畢,讓我們回到 save 方法,接著往下看,可以看到 Laravel 最后做了一個判斷:
~~~
if ($saved) {
$this->finishSave($options);
}
~~~
只有在 update 成功的情況下,才會觸發 finishSave() 方法,此方法會接收一個參數 $options,即是修改的屬性。
讓我們點開此方法:
~~~
protected function finishSave(array $options)
{
$this->fireModelEvent('saved', false);
if ($this->isDirty() && ($options['touch'] ?? true)) {
$this->touchOwners();
}
$this->syncOriginal();
}
~~~
可以看到在此方法內會觸發 saved 事件
分析完畢,大概可以做一個總結了。
當模型已存在,不是新建的時候,依次觸發的順序是:
saving -> updating -> updated -> saved
當模型不存在,需要新增的時候,依次觸發的順序則是
saving -> creating -> created -> saved
那么 saving,saved 和 updating,updated 到底有什么區別呢?
上面已經講過,Laravel 的 Eloquent 會維護實例的兩個數組,分別是 original 和 attributes。
只有在 saved 事件觸發之后,Laravel 才會對兩個數組執行 syncOriginal 操作,這樣就很好理解了。
updating 和 updated 會在數據庫中的真值修改前后觸發。
saving 和 saved 則會在 Eloquent 實例的 original 數組真值更改前后觸發。
這樣我們就可以根據業務場景來選擇更合適的觸發事件了~
**Observer (觀察者)**
如果你想在一個模型中監聽多個事件,那么你可以把它寫成一個類,類中的方法名稱即是你想要監聽的事件名稱
~~~
class UserObserver
{
/**
* 監聽數據即將創建的事件。
*
* @param User $user
* @return void
*/
public function creating(User $user)
{
}
/**
* 監聽數據創建后的事件。
*
* @param User $user
* @return void
*/
public function created(User $user)
{
}
/**
* 監聽數據即將更新的事件。
*
* @param User $user
* @return void
*/
public function updating(User $user)
{
}
/**
* 監聽數據更新后的事件。
*
* @param User $user
* @return void
*/
public function updated(User $user)
{
}
/**
* 監聽數據即將保存的事件。
*
* @param User $user
* @return void
*/
public function saving(User $user)
{
}
/**
* 監聽數據保存后的事件。
*
* @param User $user
* @return void
*/
public function saved(User $user)
{
}
/**
* 監聽數據即將刪除的事件。
*
* @param User $user
* @return void
*/
public function deleting(User $user)
{
}
/**
* 監聽數據刪除后的事件。
*
* @param User $user
* @return void
*/
public function deleted(User $user)
{
}
/**
* 監聽數據即將從軟刪除狀態恢復的事件。
*
* @param User $user
* @return void
*/
public function restoring(User $user)
{
}
/**
* 監聽數據從軟刪除狀態恢復后的事件。
*
* @param User $user
* @return void
*/
public function restored(User $user)
{
}
}
~~~
然后在 AppServiceProvider 中注冊此觀察者
~~~
<?php
namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 運行所有應用.
*
* @return void
*/
public function boot()
{
// 為 User 模型注冊觀察者
User::observe(UserObserver::class);
}
/**
* 注冊服務提供.
*
* @return void
*/
public function register()
{
//
}
}
~~~
然后你就可以在你注冊的 Observer 中觀測到各種事件啦~
- 配置
- composer安裝
- composer用法
- composer版本約束表達
- phpstorm
- sftp文件同步
- php類型約束
- laradock
- 配置文件緩存詳解
- git
- 自定義函數
- 核心概念
- IOC
- 服務提供者
- Facade
- 契約
- 生命周期
- 路由
- 請求
- 命名路由
- 路由分組
- 資源路由
- 控制器路由
- 響應宏
- 響應
- Command
- 創建命令
- 定時任務
- console路由
- 執行用戶自定義的定時任務
- artisan命令
- 中間件
- 創建中間件
- 使用中間件
- 前置和后置
- 詳細介紹
- 訪問次數限制
- 為 VerifyCsrfToken 添加過濾條件
- 單點登錄
- 事件
- 創建
- ORM
- 簡介
- DB類
- 配置
- CURD
- queryScope和setAttribute
- 查看sql執行過程
- 關聯關系
- 一對一
- 一對多
- 多對多
- 遠程關聯
- 多態一對多
- 多態多對多
- 關聯數據庫的調用
- withDefault
- 跨模型更新時間戳
- withCount,withSum ,withAvg, withMax,withMin
- SQL常見操作
- 模型事件
- 模型事件詳解
- 模型事件與 Observer
- deleted 事件未被觸發
- model validation
- ORM/代碼片段
- Repository模式
- 多重where語句
- 中間表類型轉換
- Collection集合
- 新增的一些方法
- 常見用法
- 求和例子
- 機場登機例子
- 計算github活躍度
- 轉化評論格式
- 計算營業額
- 創建lookup數組
- 重新組織出表和字段關系并且字段排序
- 重構循環
- 其他例子
- 其他問題一
- 去重
- 第二個數組按第一個數組的鍵值排序
- 搜索ES
- 安裝
- 表單
- Request
- sessiom
- Response
- Input
- 表單驗證
- 簡介
- Validator
- Request類
- 接口中的表單驗證
- Lumen 中自定義表單驗證返回消息
- redis
- 廣播事件
- 發布訂閱
- 隊列
- 守護進程
- redis隊列的坑
- beanstalkd
- rabbitmq
- redis隊列
- 日志模塊
- 錯誤
- 日志詳解
- 數據填充與遷移
- 生成數據
- 數據填充seed
- migrate
- 常見錯誤
- Blade模板
- 流程控制
- 子視圖
- URL
- 代碼片段
- Carbon時間類
- 一些用法
- 郵件
- 分頁
- 加密解密
- 緩存
- 文件上傳
- 優化
- 隨記
- 嵌套評論
- 判斷字符串是否是合法的 json 字符串
- 單元測試
- 計算出兩個日期的diff
- 自定義一個類文件讓composer加載
- 時間加減
- 對象數組互轉方法
- 用戶停留過久自動退出登錄
- optional 輔助方法
- 文件下載
- Api
- Dingo api
- auth.basic
- api_token
- Jwt-Auth
- passport
- Auth
- Authentication 和 Authorization
- Auth Facade
- 授權策略
- Gates
- composer包
- debug包
- idehelp包
- image處理
- 驗證碼
- jq插件
- 第三方登錄
- 第三方支付
- log顯示包
- 微信包
- xss過濾
- Excel包
- MongoDB
- php操作
- 聚合查詢
- 發送帶附件郵件
- 中文轉拼音包
- clockwork網頁調試
- emoji表情
- symfony組件
- swooletw/laravel-swoole
- 常見問題
- 跨域問題
- Laravel隊列優先級的一個坑
- cache:clear清除緩存問題
- .env無法讀取
- 源碼相關基礎知識
- __set和__get
- 依賴注入、控制反轉和依賴倒置原則
- 控制反轉容器(Ioc Container)
- 深入服務容器
- call_user_func
- compact
- 中間件簡易實現
- array_reduce
- 中間件實現代碼
- Pipeline管道操作
- composer自動加載
- redis延時隊列
- 了解laravel redis隊列
- cli
- 源碼解讀
- Facade分析
- Facade源碼分析
- IOC服務容器
- 中間件原理
- 依賴注入淺析
- 微信
- 微信公眾號
- 常用接收消息
- 6大接收接口
- 常用被動回復消息
- 接口調用憑證
- 自定義菜單
- 新增素材
- 客服消息
- 二維碼
- 微信語音
- LBS定位
- 網頁授權
- JSSDK
- easywechat
- 小程序
- 小程序配置app.json