Observer 觀察者模式

Observer

含義

定義對象之間的一對多依賴關(guān)系刺洒,以便當(dāng)一個(gè)對象更改狀態(tài)時(shí),它的所有依賴關(guān)系將被自動(dòng)通知和更新

適用性

當(dāng)希望對目標(biāo)對象的特定事件做出反應(yīng)時(shí)挪鹏,將使用觀察者模式袁辈。什么時(shí)候目標(biāo)對象分發(fā)生變化,就通知觀察者挖滤。其實(shí),PHP 已經(jīng)有了觀察者模式的內(nèi)置接口:SplSubjectSplObserver

抽象結(jié)構(gòu)

  • SplSubject 是一個(gè)抽象類或者接口浅役。其內(nèi)部可以定義一個(gè)數(shù)組保存附加的觀察者們. SplSubject 包含三種方法斩松。 attach 方法將觀察者添加到目標(biāo)對象,detach 方法分離觀察者觉既。 notify 方法通常包含循環(huán)惧盹,遍歷所有附加的觀察者并調(diào)用其更新方法。
  • SplObserver 也是一個(gè)抽象類或者接口奋救。SplObserver 內(nèi)有一個(gè) update 方法岭参,由于 SplSubject 觸發(fā)目標(biāo)對象的更新反惕。
  • RealSubjectSplSubject 接口的實(shí)現(xiàn)尝艘。 包含存儲附加觀察者的對象實(shí)例(數(shù)組/集合等),實(shí)際上是標(biāo)準(zhǔn)里 SplObjectStorage 類的實(shí)例姿染,可以看做是一個(gè)整潔的助手類(helper class)背亥,該類提供了一種對象到數(shù)據(jù)的映射,具體參見手冊悬赏。
  • RealObserverSplObserver 接口的實(shí)現(xiàn)狡汉。它的 update 方法傳遞一個(gè) SplSubject 實(shí)例對象作為參數(shù),任何時(shí)候目標(biāo)對象發(fā)生變動(dòng)闽颇,它就會執(zhí)行相應(yīng)的業(yè)務(wù)邏輯

示例

在這個(gè)例子中盾戴,將使用 PHP 內(nèi)置的 SplObserverSplSubject 來顯示一個(gè)相當(dāng)通用的觀察者模式,之后兵多,探索一下 Laravel Eloquent 模型內(nèi)部結(jié)構(gòu)尖啡,以及如何使用事件來處理附加的觀察者橄仆。

SPL Obeserver

使用通用的 SPL 接口,只需定義兩個(gè)類衅斩,為了演示方面盆顾,不作命名空間區(qū)分


<?php


class RealSubject implements \SplSubject
{
    private $observers;

    public function __construct($name)
    {
        $this->name = $name;
        $this->observers = new \SplObjectStorage();
    }

    public function attach(\SplObserver $observer)
    {
        // TODO: Implement attach() method.
        $this->observers->attach($observer);
    }

    public function detach(\SplObserver $observer)
    {
        // TODO: Implement detach() method.
        $this->observers->detach($observer);
    }

    public function notify()
    {
        // TODO: Implement notify() method.
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
}

class RealObserver implements \SplObserver
{
    private $name;
    public function __construct($name)
    {
        $this->name = $name;
    }


    public function update(\SplSubject $subject)
    {
        // TODO: Implement update() method.
        print "{$this->name} was notified by {$this->name}" . PHP_EOL;
    }
}

$subject1 = new RealSubject('subject1');
$observer1 = new RealObserver('observer1');
$observer2 = new RealObserver('observer2');
$observer3 = new RealObserver('observer3');

$subject1->attach($observer1);
$subject1->attach($observer2);
$subject1->attach($observer3);
$subject1->notify();



測試結(jié)果:

observer1 was notified by subject1
observer2 was notified by subject1
observer3 was notified by subject1

RealSubjectRealObserver,都是通用的實(shí)現(xiàn)方式畏梆,但是有其很大局限性您宪,這些通用的方法是約束的,你不能在 update 方法里添加任何除了 SplSubject 類的類型提示(type hint)奠涌,你可能也并不總是希望通知的方式是公開的宪巨,而希望是內(nèi)部流程發(fā)生時(shí),觀察者才得到通知溜畅,正是使用通用 SPL 接口的限制揖铜,這里你無法更改。那怎么辦呢达皿?

抽煙的故事

我們都知道人是復(fù)雜的天吓,可以是觀察者也可以是被觀察者,或者說他既是 目標(biāo)對象(Subject) 又是 觀察者(Observer)峦椰,下面通過一個(gè)故事來模擬下:

大學(xué)通宵打 LOL 或 Dota 的時(shí)候龄寞,什么不能少?香煙汤功!沒錯(cuò)物邑,尤其后半夜鬧煙慌了,沒煙抽了滔金,這時(shí)候你只剩唯一一根色解,剛一點(diǎn)著,室友聞到了餐茵,挖槽科阎,不給我來一口,這時(shí)候你咋辦忿族?是我就趕緊跑出去自己偷偷吸起來锣笨,哈哈,開玩笑道批。下面用代碼模擬下這個(gè)情景错英,這里 “你” 可以看作目標(biāo)對象,“室友” 看作觀察者隆豹,我們期望的輸出應(yīng)該是這樣:

--- 你 點(diǎn)著了一根 芙蓉王 ---

小明說:    “擦椭岩,我聞到了芙蓉王的味道”

小賤說:    “擦,我聞到了芙蓉王的味道”

小王說:    “擦,我聞到了芙蓉王的味道”

你說:      “握草判哥,一幫畜生氮唯,我先來一口”

--- 你 點(diǎn)著了一個(gè)根 中華 ---

你說: “麻蛋,幸虧還剩一根中華姨伟,哈哈”

接下來用代碼實(shí)現(xiàn):


<?php

// 先定義兩個(gè)接口惩琉,點(diǎn)煙的人,就是你夺荒,聞到香煙的室友
interface CigaretteSmeller
{
    public function smell(CigaretteSmoker $smoker, $cigarette);
}

interface CigaretteSmoker
{
    public function nearBy(CigaretteSmeller $smeller);
    public function noLongerNearBy(CigaretteSmeller $smeller);
    public function lightUp($cigarette);
}

// 給故事里的人都起個(gè)名字瞒渠,定義一個(gè)說話的方法
class Person implements CigaretteSmoker, CigaretteSmeller
{
    public function __construct($name)
    {
        $this->name = $name;
        $this->observers = new \SplObjectStorage();
    }

    public function says($phrase)
    {
        print "{$this->name} 說: \t\"" . $phrase . "\"" . PHP_EOL;
    }
    
    // 實(shí)現(xiàn)接口中的 nearBy 方法
    public function nearBy(CigaretteSmeller $smeller)
    {
        $smellers = func_get_args();
        foreach ($smellers as $smeller) {
            $this->observers->attach($smeller);
        }
    }
    
    // 實(shí)現(xiàn)接口中的 noLongerNearBy 方法
    public function noLongerNearBy(CigaretteSmeller $smeller)
    {
        $smellers = func_get_args();
        foreach ($smellers as $smeller) {
            $this->observers->detach($smeller);
        }
    }
    
    // 實(shí)現(xiàn)接口中的 lightUp 方法,你一點(diǎn)著香煙附近的室友就靠過來
    public function lightUp($cigarette)
    {
        print "--- {$this->name} lightUp {$cigarette} ---" . PHP_EOL;
        foreach ($this->observers as $observer) {
            $observer->smell($this, $cigarette);
        }
    }
    // 實(shí)現(xiàn)接口中的 smell 方法
    public function smell(CigaretteSmoker $smoker, $cigarette)
    {
        $this->says("擦技扼,我聞到了{(lán)$cigarette}的味道");
    }

}

$you = new Person('你');
$wang = new Person('小王');
$li = new Person('小李');
$jian = new Person('小賤');

$you->nearBy($wang, $li, $jian);
$you->lightUp('芙蓉王');
$you->says('握草伍玖,一幫畜生,我先來一口');

$you->noLongerNearBy($wang, $li, $jian);
$you->lightUp('芙蓉王');
$you->says("麻蛋剿吻,幸虧還剩一根中華窍箍,哈哈");

哈哈。是不是稍微理解了 Observers 模式的應(yīng)用呢丽旅。這里與前面通用的 SPL 示例不同椰棘,這段代碼反映了一定的業(yè)務(wù)邏輯,兩者都使用到了觀察者模式榄笙。接下來我們看看 ObserverEloquent 里是如何應(yīng)用的邪狞。

Eloquent Observer:開箱即用

所有的 Eloquent 模型都內(nèi)置了觀察者模式。我們創(chuàng)建一個(gè) Car 模型茅撞,汽車會有些基本屬性:制造商帆卓,車輛識別碼(vin),年代米丘,描述等剑令。

app/Car.php

namespace App;
use Illuminate\Datebase\Eloquent\Model;
class Car extends Model
{
    
}

怎么在這個(gè) model 上設(shè)置觀察者呢,很簡單拄查,使用 observe 方法吁津。

app/test1.php

\App\Car::observe(new Observers\ObserveEverything);

創(chuàng)建了一個(gè)通用的觀察者,名為 ObserveEverything靶累,它包含了所有可以在 Eloquent 模型上使用的那些開箱即用的方法腺毫。簡單起見,每個(gè)方法打印一個(gè)語句挣柬,以便了解何時(shí)被調(diào)用的。方法列表如下:

app/Observers/ObserveEverything.php

class ObserveEverything
{
    public function creating($model)
    {
        print "creating model" . PHP_EOL;
    }       
    public function created($model)
    {
        print "created model" . PHP_EOL;    
    }
    public function updating($model)
    {
        print "updating model" . PHP_EOL;
    }
    public function updated($model)
    {
        print "updated model" . PHP_EOL;
    }
    public function saving($model)
    {
        print "saving model" . PHP_EOL;
    }
    public function saved($model)
    {
        print "saved model" . PHP_EOL;  
    }
    public function deleting($model)
    {
        print "deleting model" . PHP_EOL;
    }
    public function deleted($model)
    {
        print "deleted model" . PHP_EOL;
    }
    public function restoring($model)
    {
        print "restoring model" . PHP_EOL;
    }
    public function restored($model)
    {
        print "restored model" . PHP_EOL;
    }
}

從每個(gè)方法名當(dāng)中你或許已經(jīng)知道何時(shí)被調(diào)用的睛挚。不過我還是稍微解釋下:

  • 當(dāng) model 首次在數(shù)據(jù)庫被創(chuàng)建之前邪蛔,creating 方法被觸發(fā)≡可以通過新建 model 的 save 方法或靜態(tài) create 方法來觸發(fā)侧到。注意勃教,當(dāng)新的 model 已經(jīng)被構(gòu)建或檢索時(shí),不會觸發(fā)該方法匠抗。如果方法返回 false故源,則 model 不會被創(chuàng)建。
  • 當(dāng) model 已經(jīng)在數(shù)據(jù)庫創(chuàng)建完成后汞贸,created 方法被觸發(fā)绳军。
  • 當(dāng)已經(jīng)存在的 model 在被更新之前,updating 方法會被觸發(fā)矢腻。同理门驾,返回 false ,model 不會被更新多柑。
  • 當(dāng)已經(jīng)存在的 model 被更新之后奶是,updated 方法被觸發(fā)。
  • 當(dāng) model 被創(chuàng)建或者更新之前竣灌,saving 方法會被觸發(fā)聂沙。同理,返回 false model 不會被保存初嘹。
  • 當(dāng) model 被創(chuàng)建或者更新之后逐纬,saved 方法被觸發(fā)
  • 當(dāng) model 被刪除之前,deleting 方法被觸發(fā)削樊。同理豁生,返回 false,model 不會被刪除漫贞。
  • 當(dāng) model 被刪除之后 deleted 方法被調(diào)用甸箱。
  • 當(dāng) model 被還原之前 restoring 方法被調(diào)用,還原適用于應(yīng)用了軟刪除(soft deletes)的模型迅脐。它將刪除當(dāng)前記錄的 deleted_at 列芍殖。同理,返回 false谴蔑,model 不會被刪除豌骏。
  • 當(dāng) model 被還原之后 restored 方法被調(diào)用。

接下來隐锭,添加該 Obeserver 到 Car model 上窃躲。

app/test1.php

Car::observe(new Observers\ObserveEverything);

當(dāng) Car 模型發(fā)生一系列改變時(shí)將觸發(fā) ObserveEverything 觀察者的調(diào)用。

app/test1.php

$car1 = Car::find(1);
$car->vin = str_random(32)
print "Saving car #1 to database" . "<br>";
$car1->save();

輸出:

Saving car #1 to database
saving model
updating model
updated model
saved model

app/test1.php

$car2 = new \App\Car();
   $car2->description = "cool car description";
   $car2->vin = str_random(32);
   $car2->manufacturer = "Honda";
   $car2->year = '2012';
   print "Creating a new car";
   $car2->save();

輸出:

Creating new car
saving model
creating model
created model
saved model

app/test1.php

print "Deleting a car that you just made";
$car2->delete();
 

輸出:

Deleting that new car you just made
deleting model
deleted model

app/test1.php

print "Restoring that car you just deleted";
$car2->restore();

輸出:

Restoring that car you just deleted
restoring model
saving model
updating model
updated model
saved model
restored model

使用觀察者阻止更新

上面我們重現(xiàn)了這些開箱即用的事件模式钦睡。里面的每個(gè)方法都與 Eloquent model 事件相掛鉤蒂窒。前面我們說了在相應(yīng)事件返回 false,則對應(yīng) model 將不會執(zhí)行,這意味著我們就可以在這里做些事情洒琢,來阻止 save秧秉,create,delete衰抑,restore 等象迎。來看一個(gè)例子,假設(shè)說所有的汽車 vin 碼必須包含字母 h 才能保存呛踊,那么不包含字母 h 的模型就不會寫入數(shù)據(jù)庫砾淌。

app/test1.php

Car::observe(new Observers\VinObserver);
$car1 = Car::find(1);

// attempt #1 with no h
$car1->vin = "asdfasdfasdf";
$car1->save() && print "attempt #1 saved\n";
// attempt #2 contains h
$car1->vin = "hasdfasdfasdf";
$car1->save() && print "attempt #2 saved\n";

輸出:

model vin does not contain letter 'h', canceling update...
attempt #2 saved

來看一下怎么實(shí)現(xiàn)
app/Observers/VinObserver.php

namespace App\Observers;
class VinObserver
{
    public function updating($model)
    {
        $origin = $model->getOriginal("vin");
        if ($model->vin === $original) {
            return true; // ignore unchanged vin
        }
        if (! str_contains($model->vin, 'h')) {
            print "model vin does not contain letter 'h', canceling updating vi \n";
            return false;
        }
    }
}

忽略所有未改變 vin 碼的 model。并且不包含字母 h 的 vin 返回 false恋技,這樣就可以阻止更新拇舀。來看 laravel 底層是如何處理的。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php

/**
     * 執(zhí)行模型更新操作
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return bool
     */
    protected function performUpdate(Builder $query)
    {
        // 如果更新時(shí)間返回 false蜻底,我們將取消更新操作骄崩,以便開發(fā)人員可以將驗(yàn)證系統(tǒng)
        // 掛接到其模型中,并在模型未通過驗(yàn)證時(shí)取消此操作薄辅。否則要拂,我們就更新
        if ($this->fireModelEvent('updating') === false) {
            return false;
        }

        // 首先,為了開發(fā)者方便站楚,我們需要?jiǎng)?chuàng)建一個(gè)新的查詢實(shí)例脱惰,并接觸我們維護(hù)的
        // 模型上的創(chuàng)建和更新時(shí)間戳。然后繼續(xù)保存模型實(shí)例窿春。
      
        if ($this->timestamps) {
            $this->updateTimestamps();
        }

        // 一旦我們執(zhí)行了更新操作拉一,就將觸發(fā)該模型實(shí)例的 update 事件。這將允許開發(fā)者
        // 有機(jī)會在模型更新后掛鉤處理的事情旧乞,
     
        $dirty = $this->getDirty();

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

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

        return true;
    }

當(dāng)執(zhí)行更新操作時(shí)蔚润,其中一件事就是檢查臟字段(dirty fields)。如果模型沒有發(fā)生改變尺栖,就不要更新嫡纠,接下來是 fireModelEvent 方法,作用就是觸發(fā)模型事件延赌,如果返回 false除盏,就不執(zhí)行更新操作,繼續(xù)看看 fireModelEvent 方法內(nèi)部挫以。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php


 /**
     * 觸發(fā)模型上給定的事件
     *
     * @param  string  $event
     * @param  bool  $halt
     * @return mixed
     */
    protected function fireModelEvent($event, $halt = true)
    {
        if (! isset(static::$dispatcher)) {
            return true;
        }

        // 我們把類的名稱附加到事件中者蠕,以區(qū)別于被觸發(fā)的其他模型事件,從而
        // 允許我們單獨(dú)監(jiān)聽每個(gè)模型事件屡贺,而不是捕獲所有模型的事件蠢棱。
        
        $event = "eloquent.{$event}: ".static::class;

        $method = $halt ? 'until' : 'fire';

        return static::$dispatcher->$method($event, $this);
    }
    

這個(gè)方法內(nèi)部調(diào)用了調(diào)度器(dispatcher)的 until 或者 fire 方法并返回結(jié)果锌杀。你附加到模型上的所有觀察者都放在調(diào)度器中甩栈。這就是為什么你在這個(gè) fireModelEvent 方法中沒有看到觀察者的內(nèi)容泻仙。那么這個(gè)靜態(tài)調(diào)度器到底是個(gè)什么東西呢? Eloquent 模型使用了共享的調(diào)度器量没,尤其是 $app['events'] 單例玉转。該事件調(diào)度器是消息總線,它是 Illuminate\Events\Dispatcher 的一個(gè)實(shí)例殴蹄。當(dāng)應(yīng)用程序啟動(dòng)數(shù)據(jù)服務(wù)提供者時(shí)究抓,事件調(diào)度器將被注入到 Eloquent 模型中。

vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php

    /**
     * 啟動(dòng)應(yīng)用程序事件
     *
     * @return void
     */
    public function boot()
    {
        Model::setConnectionResolver($this->app['db']);

        Model::setEventDispatcher($this->app['events']);
    }

這里袭灯,我們已經(jīng)發(fā)現(xiàn)模型事件如何被觸發(fā)以及如何阻止模型的更新操作刺下。但是還有一點(diǎn),假設(shè)所有注冊的模型事件都被放在事件調(diào)度器中處理稽荧,你不知道內(nèi)部如何處理的橘茉,接下來看一看觀察者是如何附加到模型上的。

vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php

之前版本注冊模型觀察者等一系列方法放在 model.php姨丈,里畅卓,后面 laravel 將這部分功能以 trait 的方式重構(gòu)了,并命名為 HasEvents蟋恬。所以路徑是
Illuminate\Database\Eloquent\Concerns\HasEvents.php

    /**
     * 注冊模型的觀察者
     *
     * @param  object|string  $class
     * @param  int  $priority
     * @return void
     */
    public static function observe($class, $priority = 0)
    {
        $instance = new static;

        $className = is_string($class) ? $class : get_class($class);

        // 當(dāng)注冊模型觀察者時(shí)翁潘,我們將遍歷可能的事件并確定此觀察者是否具有該方法
        // 如果有,我們將把它掛鉤到模型的事件系統(tǒng)中歼争,以便監(jiān)聽拜马。
        foreach ($instance->getObservableEvents() as $event) {
            if (method_exists($class, $event)) {
                static::registerModelEvent($event, $className.'@'.$event, $priority);
            }
        }
    }

當(dāng)在模型上調(diào)用觀察者方法時(shí),它會遍歷可能出現(xiàn)的事件沐绒,然后使用事件和類名稱做參數(shù)調(diào)用 registerModelEvent 方法俩莽。getObservableEvents 方法返回一個(gè)字符串?dāng)?shù)組。字符串便是前面提及的那些事件(updating, updated, creating, created 等等)洒沦,它還包括模型屬性數(shù)組 $observables 里存放的其他任何可被觀察(監(jiān)聽)的事件豹绪。根據(jù)源碼得知,該方法具有以下參數(shù):

static::registerModelEvent('updating', 'Observers\VinObserver@updating');

那么問題來了申眼,registerModelEvent 方法究竟做了什么瞒津?

    /**
     * 使用調(diào)度器注冊模型事件
     *
     * @param  string  $event
     * @param  \Closure|string  $callback
     * @param  int  $priority
     * @return void
     */
    protected static function registerModelEvent($event, $callback, $priority = 0)
    {
        if (isset(static::$dispatcher)) {
            $name = static::class;

            static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback, $priority);
        }
    }

共享的調(diào)度器被告知去監(jiān)聽 App\Car 的更新事件,任何時(shí)候在調(diào)度器上觸發(fā)該事件括尸,對應(yīng)回調(diào)也將觸發(fā)巷蚪。這就是觀察者如何附加到模型上的。注意濒翻,每個(gè)模型都沒有得到自己的觀察者數(shù)組屁柏。這將會比附加觀察者數(shù)組到每一個(gè)模型實(shí)例上使用更少的內(nèi)存空間啦膜。這也意味著我們所有的 Car 模型都有相同的觀察者,這是符合預(yù)期的淌喻。如果你就想給 Car 的其中一個(gè)實(shí)例創(chuàng)建觀察者僧家,而其他實(shí)例并不創(chuàng)建,那么就需要做一些不同的處理÷闵荆現(xiàn)在八拱,我們已經(jīng)明白了觀察者是如何附加到 Eloquent 模型上并被觸發(fā)。

我們已經(jīng)得知 laravel 提供的一些列標(biāo)準(zhǔn)事件可以幫助我們實(shí)現(xiàn)好多功能涯塔。然后〖〉荆現(xiàn)實(shí)情況是復(fù)雜的,對應(yīng)的 model 也是復(fù)雜的匕荸,你可能需要好多不同的標(biāo)準(zhǔn)事件以外的事件爹谭。比如你有一個(gè)不同狀態(tài)的 content 模型,對應(yīng)狀態(tài)可能是草稿榛搔,待審核诺凡,已發(fā)布等。如果你想根據(jù)不同狀態(tài)執(zhí)行不能的 model 行為药薯,那么你就需要自定義觀察者附加到模型上绑洛,去監(jiān)聽對應(yīng)事件。

前面已經(jīng)提及到模型屬性數(shù)組 $observable童本。這里就是存放我們自定義的事件真屯。該數(shù)組允許你去監(jiān)聽其他事件。比如:

<?php

use Illuminate\Database\Eloquent\Model;

class MyContentModel extends Model
{
  /**
   * Additional observable events.
   */
  protected $observables = [
    'sentforreview',  // 即將發(fā)布進(jìn)入待審核狀態(tài)
    'rejected',       // 撤銷穷娱,駁回绑蔫,意味著內(nèi)容可能因?yàn)閷徍耸д`而發(fā)布了,馬上下架
  ];
}

本質(zhì)上泵额,它使用了 Eloquent 內(nèi)部的 getObservableEvents 方法配深,前面已提及,該方法會合并自定義的事件到標(biāo)準(zhǔn)事件數(shù)組中嫁盲。

Illuminate\Database\Eloquent\Concerns\HasEvents

    /**
     * 獲取能監(jiān)聽到的事件名
     *
     * @return array
     */
    public function getObservableEvents()
    {
        return array_merge(
            [
                'creating', 'created', 'updating', 'updated',
                'deleting', 'deleted', 'saving', 'saved',
                'restoring', 'restored',
            ],
            $this->observables  // <--- merge in custom events
        );
    }

緊接著淹魄,生成對應(yīng)觀察者實(shí)現(xiàn):

<?php

namespace App\Observers;

class MyContentObserver
{

  public function sentforreview(MyContent $myContent)
  {
    // Your logic here.
  }

  public function rejected(MyContent $myContent)
  {
    // Your logic here.
  }

}

這一步我們就完成了自義定的事件等待觸發(fā)了衍锚,那么什么時(shí)候觸發(fā),怎么觸發(fā)?

<?php

use Illuminate\Database\Eloquent\Model;

class MyContentModel extends Model
{
  public function sendForReview()
  {
     // 對模型作相應(yīng)的處理然后觸發(fā)事件
     $this->fireModelEvent('sentforreview', false);
  }

  public function reject()
  {
     // 對模型作相應(yīng)的處理然后觸發(fā)事件
     $this->fireModelEvent('rejected', false);
  }
}

到這里我們就擁有了一個(gè)單一的觀察者來處理自定義事件包蓝。在最新版本 5.4 里始苇,laravel 提供了一種 map 方式搁拙,允許我們將 Eloquent 模型的生命周期的多個(gè)事件映射到專門的事件系統(tǒng)中的事件類(EventServiceProvider)云挟,關(guān)于 laravel 的事件系統(tǒng),請參考文檔哺哼。這將使我們的 model 更輕量佩抹〉鸱纾基本示例:

<?php

namespace App;

use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * 模型事件映射。
     *
     * @var array
     */
    protected $events = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}


關(guān)于這個(gè)特性棍苹,請參考
https://laracasts.com/series/whats-new-in-laravel-5-4/episodes/10

總結(jié)

事件驅(qū)動(dòng)架構(gòu)(Event-driven architecture)是一種圍繞應(yīng)用程序狀態(tài)而設(shè)計(jì)的軟件架構(gòu)模式无宿。觀察者模式可用于這種類型的軟件架構(gòu)設(shè)計(jì)。 還有其他類似的模式廊勃。比如:

  • 中介者模式(mediator pattern)
  • 命令總線模式(command bus pattern)
  • 訂閱發(fā)布模式((subscribe/publish pattern)laravel 內(nèi)置的事件系統(tǒng)就是用的這種模式

說實(shí)話懈贺,這些內(nèi)在區(qū)別有啥不同经窖,我也搞不清坡垫,總之別太迷戀設(shè)計(jì)模式。画侣。冰悠。。

參考書籍:《Design Patterns in PHP and Laravel》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末配乱,一起剝皮案震驚了整個(gè)濱河市溉卓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搬泥,老刑警劉巖桑寨,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忿檩,居然都是意外死亡尉尾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門燥透,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沙咏,“玉大人,你說我怎么就攤上這事班套≈辏” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵吱韭,是天一觀的道長吆豹。 經(jīng)常有香客問我,道長理盆,這世上最難降的妖魔是什么痘煤? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮熏挎,結(jié)果婚禮上速勇,老公的妹妹穿的比我還像新娘。我一直安慰自己坎拐,他們只是感情好烦磁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呕乎,像睡著了一般陨晶。 火紅的嫁衣襯著肌膚如雪先誉。 梳的紋絲不亂的頭發(fā)上褐耳,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天铃芦,我揣著相機(jī)與錄音刃滓,去河邊找鬼咧虎。 笑死老客,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鳍鸵。 我是一名探鬼主播尉间,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哲嘲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了画切?” 一聲冷哼從身側(cè)響起囱怕,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤典格,失蹤者是張志新(化名)和其女友劉穎耍缴,沒想到半個(gè)月后防嗡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體本鸣,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荣德,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年涮瞻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了署咽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宁否。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慕匠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出台谊,到底是詐尸還是另有隱情,我是刑警寧澤譬挚,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站减宣,受9級特大地震影響盐须,放射性物質(zhì)發(fā)生泄漏漆腌。R本人自食惡果不足惜贼邓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一姨蟋、第九天 我趴在偏房一處隱蔽的房頂上張望立帖。 院中可真熱鬧,春花似錦堂飞、人聲如沸绰筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至年叮,卻和暖如春具被,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背只损。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工一姿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人跃惫。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓叮叹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親爆存。 傳聞我的和親對象是個(gè)殘疾皇子蛉顽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Observer 模式應(yīng)該可以說是應(yīng)用最多、影響最廣的設(shè)計(jì)模式之一终蒂,在系統(tǒng)開發(fā)架構(gòu)設(shè)計(jì)中有著很重要的地位和意義蜂林。O...
    wild_horse閱讀 644評論 0 0
  • Eloquent: 起步 簡介 Laravel 的 Eloquent ORM 提供了一種漂亮簡潔的關(guān)系映射的模型來...
    Dearmadman閱讀 11,930評論 3 16
  • 今天中午下了一場暴雨,聽著雨聲拇泣,電臺聲進(jìn)入了沉沉的夢鄉(xiāng)噪叙。夢鄉(xiāng)啊霉翔! 這真是我這大概七年來的第一次做夢睁蕾,之所以記得這么...
    沿海地帶閱讀 674評論 0 3
  • (看著老田笑著遞過來的滿盈盈的一杯啤酒,侯勇覺得他臉上的美人痣今天格外奪目) 周一,日常下井子眶,這次還是老地方瀑凝。本想...
    翱翔的窩窩頭閱讀 347評論 0 0
  • 人類一思考,上帝就發(fā)笑臭杰。比如粤咪,人為什么活著?這是個(gè)哲學(xué)問題渴杆。有些人會說寥枝,提出這個(gè)問題的人就是吃飽了撐的,瞎扯...
    一葉一沙閱讀 251評論 0 2