Laravel最佳實踐 -- 事件驅動編程

在這篇文章中咱們將瞭解到什麼是「事件驅動編程」以及在Laravel中如何開始構建一個事件驅動應用,同時咱們還將看到如何經過事件驅動編程來對應用程序的邏輯進行解耦。php

在開始以前,先說明一下這篇文章主要是闡述事件驅動這種編程思惟和理念的,因此不會涉及到Laravel Events的方方面面。若是你須要更全面地瞭解Laravel Events和它的各類用法能夠訪問Laravel Events文檔來了解詳細信息。laravel

何爲事件驅動編程

在咱們深刻事件驅動應用以前,咱們先看一下在維基百科裏對事件驅動編程的定義:git

事件驅動編程是一種編程模式,其中的程序流由諸如用戶動做(鼠標點擊,按鍵)、傳感器輸出或來自其餘程序/線程的消息等事件來決定肯定。事件驅動編程是圖形用戶界面和其餘應用程序(例如JavaScript Web應用程序)中使用的主要範例,用於執行某些操做來響應用戶輸入。github

事件驅動應用程序會響應用戶的動做,而後執行對應的代碼來響應用戶的動做。編程

Laravel Events

經過上面的定義,事件是發生在應用程序中的動做。Javascript的事件是像鼠標點擊、鼠標懸浮、按下鍵盤這樣的用戶動做。在Laravel中事件是發生在應用程序中的動做,像郵件通知、記錄日誌、用戶註冊、CRUD操做等。Laravel Events系統提供了簡易的觀察者模式實現,讓開發者可以訂閱和監聽發生在應用中的動做。app

應用中有些事件是由Laravel框架自動發起。好比說當使用Eloquent Model執行create、save、update或者delete操做時Laravel將分別發起createdsavedupdated、和deleted事件。若是須要的話咱們能夠監聽這些事件從而執行相應的代碼來完成本身的需求。除了Laravel框架自動發起的事件,咱們還能夠根據本身應用的須要讓Laravel發起咱們本身定義的事件。好比說你能夠發起一個userRegistered事件,在事件處理程序中發送用戶驗證郵件好讓新註冊的用戶可以驗證本身的郵箱。框架

發起一個事件並不會讓應用程序執行任何相應的操做,咱們必須在事件處理程序中對被髮起的事件進行相應地迴應。Laravel Events由兩部分組成Event HandlerEvent ListenerEvent Handler中包含了發起事件相關的信息。Event Listener監聽事件對象並對事件進行迴應,Event Listener是咱們實現事件邏輯的地方。在Laravel中Event類文件被存放在app/Events目錄,Listener類文件被存放在app/Listeners目錄。ide

爲什麼使用事件驅動編程

咱們已經瞭解事件驅動應用和Laravel Events的概念了,你可能會好奇爲何要採用事件驅動這種方法來構建你的應用程序。咱們來看一下事件驅動編程帶來的收益。學習

首先,事件是一種解耦應用程序各個方面的好方法,由於單個事件能夠有多個不依賴於彼此的監聽器。經過解耦,不會由於你使用了不適合域邏輯的代碼而污染了代碼庫。其次,因爲應用程序是鬆散耦合的,你能夠輕鬆擴展應用程序的功能,而沒必要打亂/重寫應用程序或應用程序的某些其餘功能。ui

應用示例

如今假設新用戶註冊了咱們的應用程序後,應用程序會給用戶發送一封歡迎郵件,同時會自動給用戶訂閱應用上的每週新聞簡報。在不該用事件驅動方式的狀況下代碼每每是以下這樣:

// without event-driven approach

public function register(Request $request)
{
    // validate input
    $this->validate($request->all(), [
      'name' => 'required',
      'email' => 'required|unique:users',
      'password' => 'required|min:6|confirmed',
    ]);

    // create user and persist in database
    $user = $this->create($request->all());

    // send welcome email
    Mail::to($user)->send(new WelcomeToSiteName($user));

    // Sign user up for weekly newsletter
    Newsletter::subscribe($user->email, [
      'FNAME': $user->fname,
      'LNAME': $user->lname
    ], 'SiteName Weekly');

    // login newly registered user
    $this->guard()->login($user);

    return redirect('/home');
}
複製代碼

你能夠看到發送歡迎郵件和訂閱新聞簡報的邏輯緊密耦合到了register方法裏, 根據關注點分離原則register方法不該該關心發送歡迎郵件和訂閱新聞簡報的具體實現。你可能會以爲發送歡迎郵件和訂閱新聞放到register方法裏也沒什麼,可是若是在註冊時除了發送郵件還要給用戶發送短信呢?繼續寫在register方法裏:

public function register(Request $request)
{
    // validate input

    // create user and persist in database

    // send welcome email
    Mail::to($user)->send(new WelcomeToSiteName($user));

    // send SMS
    Nexmo::message()->send([
      'to' => $user->phone_number,
      'from' => 'SiteName',
      'text' => 'Welcome and thanks for signup on SiteName.'
    ]);

    // Sign user up for weekly newsletter
    Newsletter::subscribe($user->email, [
      'FNAME': $user->fname,
      'LNAME': $user->lname
    ], 'SiteName Weekly');

    // login newly registered user

    return redirect('/home');
}
複製代碼

能夠看到代碼庫開始變得臃腫。如今讓咱們看看採用事件驅動編程方法如何實現上述相同的功能。

// with event-driven approach

public function register(Request $request)
{
    // validate input
    $this->validate($request->all(), [
      'name' => 'required',
      'email' => 'required|unique:users',
      'password' => 'required|min:6|confirmed',
    ]);

    // create user and persist in database
    $user = $this->create($request->all());

    // fire event once user has been created
    event(new UserRegistered($user));

    // login newly registered user
    $this->guard()->login($user);

    return redirect('/home');
}
複製代碼

一旦建立了用戶,UserRegistered事件就會被觸發。回想一下,咱們以前提到,發起一個事件後應用並不會本身作任何事情,咱們須要監聽UserRegistered事件並執行必要的操做。讓咱們建立UserRegistered事件類和SendWelcomeMail以及SignupForWeeklyNewsletter監聽器類:

php artisan make:event UserRegistered
php artisan make:listener SendWelcomeMail --event=UserRegistered
php artisan make:listener SignupForWeeklyNewsletter --event=UserRegistered
複製代碼

事件和監聽器之間的對應關係須要註冊到EventServiceProvider的$listen屬性裏:

protected $listen = [
    UserRegistered::class => [
        SendWelcomeMail::class,
        SignupForWeeklyNewsletter::class,
    ],
];
複製代碼

打開app/Events/UserRegistered.php文件更新它的構造方法:

public $user;

public function __construct(User $user)
{
  $this->user = $user;
}
複製代碼

聲明$user爲public,它將被傳遞給監聽器,而監聽器能夠用它來執行必要的邏輯。接下來,事件監聽器將在其handle方法中接收到事件實例。在handle方法中,咱們能夠執行響應事件的操做。

// app/Listeners/SendWelcomeMail.php
public function handle(UserRegistered $event)
{
  // send welcome email
  Mail::to($event->user)->send(new WelcomeToSiteName($event->user));
}


// app/Listeners/SignupForWeeklyNewsletter.php
public function handle(UserRegistered $event)
{
  // Sign user up for weekly newsletter
  Newsletter::subscribe($event->user->email, [
    'FNAME': $event->user->fname,
    'LNAME': $event->user->lname
  ], 'SiteName Weekly');
}
複製代碼

能夠看到經過事件驅動的方式咱們讓register方法的代碼儘量的少而且專一於用戶註冊這件事上,其它的邏輯由UserRegistered事件的監聽器來負責,如今若是說咱們想在用戶註冊後發送短信給新註冊的用戶,咱們所要作的就是建立一個新的事件監聽器來監聽UserRegistered事件什麼時候被觸發

php artisan make:listener SendWelcomeSMS --event=UserRegistered

// app/Listeners/SendWelcomeSMS.php
public function handle(UserRegistered $event)
{
  // send SMS
  Nexmo::message()->send([
    'to' => $event->user->phone_number,
    'from' => 'SiteName',
    'text' => 'Welcome and thanks for signup on SiteName.'
  ]);
}
複製代碼

注:記得要更新EventServiceProvider裏的$listen屬性

總結

在這篇文章中,咱們已經可以理解事件驅動的編程是什麼,事件驅動的應用程序是什麼以及Laravel事件是什麼。咱們還研究了事件驅動應用程序的優點。可是,像跟全部有積極影響的編程概念同樣,它也有缺點。事件驅動型應用程序的主要缺點是讓程序流變得複雜了,尤爲一些剛接觸開發的人可能很難真正理解應用程序的流程。以上面的實現爲例,經過register方法咱們並不能直觀地看到程序在建立用戶後會向新用戶發送一封歡迎郵件,並將其註冊到新聞通信中。

因此在開發中應該根據場景創造性地使用它,利用它的優點爲你的應用程序解耦,而不是過分使用它。

本文已經收錄在系列文章Laravel核心代碼學習裏,歡迎訪問閱讀。

相關文章
相關標籤/搜索