Eloquent Observer 的小坑

前言

最近開發新的項目不是基於完整的 laravel 框架,框架是基於 laravel ORM 的簡單MVC模式。隨着項目的成熟和業務需求,須要引入事件機制。簡單地瀏覽了一下 symfony、laravel 的事件機制,都是依賴於 container 的。感受若是引入的話開銷有點大,在尋覓更好解決方案過程當中發現 ORM 竟然自帶事件機制,徹底足夠目前的業務需求,又不須要改動框架,簡直不能太棒!這裏簡單的記錄一下orm observe 的使用吧。laravel

事件觸發流程

因爲 ORM 事件是針對 Model 的,因此自己結合 Model 封裝了一些基礎的事件。感受基本上是夠使用了,若是須要添加額外事件的話,ORM 也提供了 setObservableEvents()供使用。框架

public function getObservableEvents()
    {
        return array_merge(
            [
                'creating', 'created', 'updating', 'updated',
                'deleting', 'deleted', 'saving', 'saved',
                'restoring', 'restored',
            ],
            $this->observables
        );
    }

咱們都知道 ORM 不管是 create 仍是 update 本質是 save。所以分析一下 ORM save 時底層流程是:ui

public function save(array $options = [])
    {
        $query = $this->newQueryWithoutScopes();
        //觸發 saving 事件
        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;
    }

protected function performInsert(Builder $query, array $options = [])
    {
        //觸發 creating 事件
        if ($this->fireModelEvent('creating') === false) {
            return false;
        }
        if ($this->timestamps && Arr::get($options, 'timestamps', true)) {
            $this->updateTimestamps();
        }
        $attributes = $this->attributes;
        if ($this->getIncrementing()) {
            $this->insertAndSetId($query, $attributes);
        }
     else {
            $query->insert($attributes);
        }
        $this->exists = true;
        $this->wasRecentlyCreated = true;
        //觸發 created 事件
        $this->fireModelEvent('created', false);
        return true;
        }

protected function finishSave(array $options)
    {
        //觸發 saved 事件
        $this->fireModelEvent('saved', false);
        $this->syncOriginal();
        if (Arr::get($options, 'touch', true)) {
            $this->touchOwners();
        }
    }

以上是闡述 creat 時事件觸發流程,顯而易見依次是 saving>creating>created>saved。this

update 同理就不具體貼代碼了, saving>updating>updated>saved。spa

而 delete 不涉及 save,所以依次只觸發了 deleting 和deleted。 當 restore 軟刪除記錄時觸發了 restoring 和 restored 方法。rest

使用

只需在 model 添加 boot 方法,能夠直接在 boot 方法中定義事件,也能夠面向於 model 新建 modelObserver 集中處理對應的 model 事件。code

public static function boot()
    {
        parent::boot();
        static::setEventDispatcher(new \Illuminate\Events\Dispatcher());
        //static::saving(function($model){  });
        static::observe(new testObserver());
    }

class testObserve{
 public function saving($model)
    {
        $model->name=name.'test';
    }
}

大坑

不得不感嘆這個事件真的既簡單又實用,可是!當我在開發過程當中有一處 update 怎麼都不會觸發事件,捉急的很啊。菜鳥一下看不出問題本質,只能打斷點一點點排查。發現是由於該處代碼前調用了belongsToMany()orm

  • 痛點1:dispatcher 與 container 共生死單例。
    多對多模型關聯 boot 兩個model,所以 boot model2 時,將 dispatcher 指向了 model2。
  • 痛點2:model 只 boot 一次
    bootIfNotBooted(),booted model 不會從新調用 boot 方法。symfony

  • 結果:update model1 怎麼都沒法觸發事件。
  • 解決:目前能想到的即是在調用 belongsToMany()時,調用 ORM 的 clearBootedModels()方法,清除 bootedModel,update model1 時再次 boot model1,強行將 dispatcher 指向 model1.
相關文章
相關標籤/搜索