Laravel Eloquent save 触发了哪些事件

laravel Eloquent save 触发了哪些事件

记录一下laravel Eloquent 事件的使用

需求是这样的,如何在订单状态改变的时候给对应的用户发送消息,拽过来同事开始讨论

  1. 当然第一个想到的就是用Eloquent的事件,订单状态变了,会触发事件,但是Eloquent的这些updated,saved事件怎么知道是订单状态变了,而不是其他值改变了触发的呢?消息里面需要用到订单原来的属性怎么办?
  2. 或者就是controller处理完逻辑,改变了订单的状态自己触发一个事件,
    那么是不是触发事件需要传入原来的订单的属性和改变后的属性呢?这样listener才能确切的知道订单的状态是怎么变得,然后发送对应的消息?
  3. 或者压根不触发事件,直接在controller中处理这个逻辑,因为controller明白到底发生了什么,直接发送消息,想怎么发怎么发

我们有好多好多的订单,好多好多的地方会改变订单状态,显然2,3两个方案太扯淡了,每个处理逻辑的地方都需要增加代码,处理一堆逻辑,写着就不爽,维护起来不是要疯了?


好吧我们来看看laravel Eloquent save的时候到底触发了哪些事件

Illuminate/Database/Eloquent/Model.php

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

     if ($this->fireModelEvent('saving') === false) {
         return false;
     }

     if ($this->exists) {
         $saved = $this->performUpdate($query, $options);
     } else {
         $saved = $this->performInsert($query, $options);
     }

     if ($saved) {
         $this->finishSave($options);
     }

     return $saved;
 }

首先触发的当然是saving,如果saving返回的是false,那么save就失败了,返回false

接着如果$this->exists,就是这个model不是新创建的,那么就需要进行更新操作

 protected function performUpdate(Builder $query, array $options = [])
 {
     $dirty = $this->getDirty();

     if (count($dirty) > 0) {

         if ($this->fireModelEvent('updating') === false) {
             return false;
         }

         if ($this->timestamps && Arr::get($options, 'timestamps', true)) {
             $this->updateTimestamps();
         }

         $dirty = $this->getDirty();

         if (count($dirty) > 0) {
             $numRows = $this->setKeysForSaveQuery($query)->update($dirty);

             $this->fireModelEvent('updated', false);
         }
     }

     return true;
 }

看看更新干了什么,

  • 首先如果这个模型dirty了,也就是脏了,也就是有属性改变了,那么才需要更新

  • 先触发updating,更新失败了就false,

  • 更新时间戳

    这里又判断了count($dirty) > 0,为什么??更updateTimestamps有什么关系吗?没有仔细看,以后再说吧
  • 然后执行update,也就是写入了数据库

  • 最后触发updated

那么如果这个对象是新建的,也就是需要执行插入操作,performInsert也做了差不多的事,

  • 触发creating,
  • 执行insert,插入数据库,
  • 最后created

最后回到save方法,如果更新或者插入操作成功了,那么就finishSave来结束save,

finishSave 触发了saved事件, 最后syncOriginal。

小结一下

  • 新创建的对象,save依次触发 saving->creating->created->saved
  • 已存在的对象,save依次触发 saving->updating->updated->saved

好了知道了我们知道了model save的时候触发了哪些事件,但是还是没有解决我们的问题啊,到底是因为状态改变了触发的还是因为订单其他的值改变了触发的呢?订单原来的状态怎么获取呢?

这就需要看看isDirty和syncOriginal是怎么回事了

打印过Eloquent 对象的应该都会发现,其实模型会有两个数组,一个original,一个attributes。

这下应该就明白了,初始化一个模型的时候这两个数组是一样的,赋值操作只是改变attributes,所以isDirty也就是根据两个数据来判断模型是否dirty了,模型触发完saved事件后才会执行syncOriginal,syncOriginal也就是将attributes赋值给original。所以上面触发的所有事件,我们都是能拿到原来的值和变化的值得。

所以我们的问题也就解决了,当模型触发updated事件的时候,我们根据isDirty(['status']) 知道订单状态改变了,然后拿到订单之前是个什么样子,改变后是个什么样子,该发什么消息也就很明朗了。

laravel设计的好牛逼,事实教育我们,要多看文档,多看代码。

本帖已被设为精华帖!
liyu001989
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 6
dividez

isDirty() 怎么理解呀?

6年前 评论
liyu001989

@dividez 就是脏了,也就是被修改过

6年前 评论
dividez

@liyu001989 是数据库的数据脏了吗?

6年前 评论
liyu001989

@dividez 是你实例化出来的这个model脏了

>>> $user = App\Models\User::find(1)
>>> $user->isDirty();
=> false
>>> $user->name = '123';
=> "123"
>>> $user->isDirty();
=> true
>>>

这样你理解不,可以看看源码,每个模型都会维护两个数组 $attributes 和 $original,记录变化的属性和原始属性

6年前 评论
dividez

@liyu001989 一下子就看明白了

6年前 评论

通过测试,如果$attributes 和 $original 的值一模一样,就不会进行更新操作

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!