簡述 Laravel Model Events 的使用

coding01發表於2018-03-30

最近一直在思考如何利用 Laravel,更進一步做出一套較為不一樣的開發框架出來。反覆看了很多有關 Laravel 框架的資料和文件,最後還是落在 Laravel Model 層上來。

發現 Model 還有很多值得學習的地方,其中 Events 讓人眼前一亮。

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

觀察者模式

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

定義物件間一種一對多的依賴關係,使得當一個物件改變狀態,則所有依賴於它的物件都會得到通知並被自動更新。

簡述 Laravel Model Events 的使用

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

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

  2. Observer 觀察者。觀察者接收到訊息後,即進行 update 更新操作,對接收到的資訊進行處理。

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

  4. ConcreteObserver 具體觀察者。每個觀察者在接收到資訊後處理的方式不同,各個觀察者有自己的處理邏輯。

觀察者模式優勢

觀察者和被觀察者之間是抽象耦合的,不管是增加觀察者還是被觀察者都非常容易擴充套件。

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

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

Laravel 的事件系統

在 Laravel 框架中,存在事件機制這種很好的應用解耦方式,因為一個事件可以擁有多個互不依賴的監聽器。例如,如果你希望每次生成訂單,或者訂單狀態由「未支付轉為支付」時,向使用者或者運營人員傳送一個簡訊或者釘釘通知。你可以簡單地發起一個 OrderSaving 事件,讓監聽器接收之後轉化成一個簡訊或者釘釘通知,這樣你就可以不用把「訂單的業務程式碼」和「訊息通知」的程式碼耦合在一起了,起到「解耦」的效果。

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

根據 ServiceProvider 的作用,程式執行時,會自動載入,所以在 Laravel 的事件系統中,EventServiceProvider 充當 Events 和 Listeners 的橋接器,也就是說,利用 EventServiceProvider 可以將 Events 和 Listeners 的關聯載入到系統中。

簡述 Laravel Model Events 的使用

從這也可以看出,一個 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 的事件系統是一個值得研究的案例。

下一步讓我們來扒一扒這背後的程式碼實現原理。

「未完待續」

相關文章