最近一直在思考如何利用 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.
定義物件間一種一對多的依賴關係,使得當一個物件改變狀態,則所有依賴於它的物件都會得到通知並被自動更新。
如上圖所示(擷取自《Head First Design Patterns》一書),主要包括四個部分:
-
Subject 被觀察者。是一個介面或者是抽象類,定義被觀察者必須實現的職責,它必須能偶動態地增加、取消觀察者,管理觀察者並通知觀察者。
-
Observer 觀察者。觀察者接收到訊息後,即進行 update 更新操作,對接收到的資訊進行處理。
-
ConcreteSubject 具體的被觀察者。定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。
-
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
時,creating
和created
兩個事件被呼叫;同理,如果資料庫中存在,執行save
方法時,updating
和updated
兩個事件被呼叫。
下面通過建立 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 的事件系統是一個值得研究的案例。
下一步讓我們來扒一扒這背後的程式碼實現原理。
「未完待續」