簡述 Laravel Model Events 的使用

最近一直在思考如何利用 Laravel,更進一步作出一套較爲不同的開發框架出來。反覆看了不少有關 Laravel 框架的資料和文檔,最後仍是落在 Laravel Model 層上來。php

發現 Model 還有不少值得學習的地方,其中 Events 讓人眼前一亮。數據庫

下面從「觀察者模式」到「Laravel 事件系統」,再到 「Model Events」,簡述 Model Events 的使用。bash

觀察者模式

Define a one-to-many dependency between objects so that when one object changes state, all its dependents aer notified and updated automatically.閉包

定義對象間一種一對多的依賴關係,使得當一個對象改變狀態,則全部依賴於它的對象都會獲得通知並被自動更新。app

如上圖所示(截取自《Head First Design Patterns》一書),主要包括四個部分:框架

  1. Subject 被觀察者。是一個接口或者是抽象類,定義被觀察者必須實現的職責,它必須能偶動態地增長、取消觀察者,管理觀察者並通知觀察者。ide

  2. Observer 觀察者。觀察者接收到消息後,即進行 update 更新操做,對接收到的信息進行處理。函數

  3. ConcreteSubject 具體的被觀察者。定義被觀察者本身的業務邏輯,同時定義對哪些事件進行通知。學習

  4. ConcreteObserver 具體觀察者。每一個觀察者在接收到信息後處理的方式不一樣,各個觀察者有本身的處理邏輯。測試

觀察者模式優點

觀察者和被觀察者之間是抽象耦合的,無論是增長觀察者仍是被觀察者都很是容易擴展。

根據單一職責原則,每一個類的職責是單一的,那麼怎麼把各個單一的職責串聯成真實的複雜的邏輯關係呢,觀察者模式能夠起到橋樑做用。

觀察者模式是鬆耦合的典型。

Laravel 的事件系統

在 Laravel 框架中,存在事件機制這種很好的應用解耦方式,由於一個事件能夠擁有多個互不依賴的監聽器。例如,若是你但願每次生成訂單,或者訂單狀態由「未支付轉爲支付」時,向用戶或者運營人員發送一個短信或者釘釘通知。你能夠簡單地發起一個 OrderSaving 事件,讓監聽器接收以後轉化成一個短信或者釘釘通知,這樣你就能夠不用把「訂單的業務代碼」和「消息通知」的代碼耦合在一塊兒了,起到「解耦」的效果。

Laravel 的事件提供了一個簡單的觀察者實現,可以訂閱和監聽應用中發生的各類事件。事件類保存在 app/Events 目錄中,而這些事件的的監聽器則被保存在 app/Listeners 目錄下。這些目錄能夠使用 Artisan 命令來生成。

根據 ServiceProvider 的做用,程序執行時,會自動加載,因此在 Laravel 的事件系統中,EventServiceProvider 充當 Events 和 Listeners 的橋接器,也就是說,利用 EventServiceProvider 能夠將 Events 和 Listeners 的關聯加載到系統中。

從這也能夠看出,一個 Event 對應着多個 Listeners,意味着能夠被多個 Listeners 監聽。

一樣的,也能夠在 boot 方法中註冊基於事件的閉包

/** * 註冊應用程序中的任何其餘事件。 * * @return void */
public function boot() {
    parent::boot();

    Event::listen('event.name', function ($foo, $bar) {
        //
    });
}
複製代碼

下面拿 Model Event 來舉例,由於 Model Event 基於 Laravel Event 系統之上。

並且相比較 Laravel Event,在常規邏輯處理時,主要是利用全局函數 event() 來觸發事件,屬於手動觸發機制。

在 Model Event,則能夠自定義在 Model 生命週期節點上「自動」觸發事件。

Model Events

一個 Model 操做主要包含如下這幾個生命節點:

節點 節點 節點
retrieved creating created
updating updated saving
saved deleting deleted
restoring restored

The retrieved event will fire when an existing model is retrieved from the database. When a new model is saved for the first time, the creating and created events will fire. If a model already existed in the database and the save method is called, the updating / updated events will fire. However, in both cases, the saving / saved events will fire.

相信這個比較好理解,但數據庫中不存在,第一次 save 時,creatingcreated 兩個事件被調用;同理,若是數據庫中存在,執行 save 方法時,updatingupdated 兩個事件被調用。

下面經過建立 Order Model 來舉例說明,怎麼使用 Laravel Event。

php artisan make:model Order -m
複製代碼

事件和監聽器

1. 註冊 Event 和 Listener

一樣的,在 EventServiceProvider 註冊關聯:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider {
    /** * The event listener mappings for the application. * * @var array */
    protected $listen = [
        'App\Events\OrderSavingEvent' => [
            'App\Listeners\OrderSavingListener',
        ],
    ];

    /** * Register any events for your application. * * @return void */
    public function boot() {
        parent::boot();

        //
    }
}

複製代碼

2. 在 Order Model 分派 Saving Event

<?php

namespace App;

use App\Events\OrderSavingEvent;
use Illuminate\Database\Eloquent\Model;

class Order extends Model {
    protected $dispatchesEvents = [
        'saving' => OrderSavingEvent::class,
    ];
}
複製代碼

3. 建立 Event 和 Listener 類

php artisan event:generate
複製代碼

在 Saving Event 中綁定 Order

<?php

namespace App\Events;

use App\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class OrderSavingEvent {
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $order;
    /** * Create a new event instance. * * @return void */
    public function __construct(Order $order) {
        $this->order = $order;
    }

    /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */
    public function broadcastOn() {
        return new PrivateChannel('channel-name');
    }
}

複製代碼

4. 編寫 Listener 類,處理監聽邏輯

<?php

namespace App\Listeners;

use App\Events\OrderSavingEvent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderSavingListener {
    /** * Create the event listener. * * @return void */
    public function __construct() {
        //
    }

    /** * Handle the event. * * @param OrderSavingEvent $event * @return void */
    public function handle(OrderSavingEvent $event) {
        info($event->order);
    }
}
複製代碼

5. 測試

$order = new Order();
$order->name = 'good_name_2';

$order->save();
複製代碼

運行結果:

[2018-03-29 12:30:24] testing.INFO: {"name":"good_name_2"} 
複製代碼

觀察者模式

若是在同一個 Model 下監聽多個 Events,總不能每一個 Event 都須要建立對應的 Listener 類吧。Laravel 提供了一個便捷的方法:建立 observer 類,把全部 Events 聚合到這個類中,而後還在 AppServiceProvider 的 boot 中,註冊這個觀察類:

<?php

namespace App\Providers;

use App\Observers\OrderObserver;
use App\Order;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider {
    /** * Bootstrap any application services. * * @return void */
    public function boot() {
        Order::observe(OrderObserver::class);
    }

    /** * Register any application services. * * @return void */
    public function register() {
        //
    }
}

複製代碼

具體 observer 類:

<?php

namespace App\Observers;


use App\Order;

class OrderObserver {

    /** * 監聽訂單建立事件 * @param Order $order */
    public function creating(Order $order) {
        info('creating');
        info($order);
    }

    public function created(Order $order) {
        info('created');
        info($order);
    }

    /** * 監聽訂單保存事件 * @param Order $order */
    public function saving(Order $order) {
        info('saving');
        info($order);
    }

    public function saved(Order $order) {
        info('saved');
        info($order);
    }
}
複製代碼

測試:

$order = new Order();
$order->name = 'good_name';

$order->save();
複製代碼

運行效果:

[2018-03-27 15:04:02] testing.INFO: saving
[2018-03-27 15:04:02] testing.INFO: {"name":"good_name"}  
[2018-03-27 15:04:02] testing.INFO: creating  
[2018-03-27 15:04:02] testing.INFO: {"name":"good_name"}  
[2018-03-27 15:04:02] testing.INFO: created  
[2018-03-27 15:04:02] testing.INFO: {"name":"good_name","updated_at":"2018-03-27 15:04:02","created_at":"2018-03-27 15:04:02","id":1}  
[2018-03-27 15:04:02] testing.INFO: saved  
[2018-03-27 15:04:02] testing.INFO: {"name":"good_name","updated_at":"2018-03-27 15:04:02","created_at":"2018-03-27 15:04:02","id":1}
複製代碼

再次更新 order:

$order = Order::find(1);
$order->name = 'good_name3';

$order->save();
複製代碼

這時候,就不會觸發 creating 和 created 事件了。運行效果:

[2018-03-27 15:11:11] testing.INFO: saving  
[2018-03-27 15:11:11] testing.INFO: {"id":1,"name":"good_name3","created_at":"2018-03-27 15:04:02","updated_at":"2018-03-27 15:04:02"}  
[2018-03-27 15:11:11] testing.INFO: saved  
[2018-03-27 15:11:11] testing.INFO: {"id":1,"name":"good_name3","created_at":"2018-03-27 15:04:02","updated_at":"2018-03-27 15:11:11"}  
複製代碼

總結

觀察者模式的做用在於:觀察者和被觀察者之間是抽象耦合的,當一個對象改變狀態,則全部依賴於它的觀察者們都會獲得通知並作對應的邏輯處理。Laravel 的事件系統是一個值得研究的案例。

下一步讓咱們來扒一扒這背後的代碼實現原理。

「未完待續」

相關文章
相關標籤/搜索