你是否聽說過單一職責原則(single responsibility principle)?我希望是的。它是程序設計的基本原則之一,它基本上的意思就是,一個類有且只有一個職責。換句話說,一個類必須且只能做一件事,而不做其他任何事。
通常,當你構建軟件的第一個版本時,一切都好說。但總會發生下面的情況。你的老板會說:是時候推出一些新的功能了。尤其是當更新意味着在這里插入一些額外的行為的時候,你的代碼庫會變得笨重和馬虎。然后你不得不與期限、測試、 Q&A 抗爭,這不是一種好的做法,對嗎?
現在,在軟件開發的世界中,你可以找到許多技術和方法,以優雅的方式為您的軟件添加新的功能。你很可能聽說過編程中的事件(Event)。
簡言之,它的邏輯就像是這樣:當 X 做這種行為的時候,那么 Y 必須做那種行為。
想象一下,在你的應用程序中類似的情況:當你完成了你的應用時,你說:“哦,我忘了給新用戶發郵件”。
在 Eloquent 中,你有兩種方式來處理這種情況。第一種方式通過模型事件 (Event) ,第二種方式基於一種更先進的概念:模型觀察者 (Observers)。
在本章中,首先你會了解 Eloquent 模型中有關事件的一切,然后會介紹:什么是事件,以及何時使用它們。然后我們對模型觀察者也是按這樣的順序做介紹。你會了解到所以的差異、優點及缺點。對於這兩種概念,我們都將用一個實際的例子來說明在現實世界中如何使用它們。
你准備好了嗎?下面就讓我們開始吧。
一、何時使用事件
什么是事件?如果你在谷歌中搜索這個詞,你會得到多個結果。例如,它會被定義為已經發生或被視為發生的一些事;一次事故,尤其是特別重大的。它也可以定義為發生在一段特定時期內特定地點的事。
我喜歡這兩個定義,因為它們與我們的內容很符合。事實上,在某種意義上,你可以把這段特定的時期看作模型的生命周期。
你可以創建一個新的實例,更新現有實例,或刪除它。你可以做的每個操作都涉及到兩個事件。
從基礎上來說:我剛剛創建了一條記錄,我刪除了那條記錄,我正在更新那條記錄,聽起來很自然,對嗎?
在當模型的生命周期中,當發生一些事的時候,Eloquent 會觸發一些事件:
- creating
- created
- updating
- updated
- saving
- saved
- deleting
- deleted
- restoring
- restored
對於每一個操作,都對應兩個獨立的事件。正如你可能想象的,它們指的是單獨的時刻。我們已創建操作作為實例:
你有一個 creating
事件,可以理解為“創建操作即將發生”,而 created
表示“事件已經發生了”。
科學家可能會說:
creating
:是表示 t – 1 時刻created
:是與 t + 1 時刻相關
所以,對於下面三個基本操作,都有兩個對應的事件:創建 (create
)、更新 (update
) 和刪除 (delete
)。
此外,你還可以看到另外兩個操作:保存 (save
) 和恢復 (restore
)。但是,請不要擔心,他們並不復雜:
- Save:你只需要知道,
save
操作是與create
和update
相關的。我們假設你需要添加一個行為,應用程序是創建一條新的記錄還是更新一條已有的記錄。難道對相同的事情還要聲明兩次嗎?只需一個普通的save
操作即可。 - Restroe:當你的某個模型用到了軟刪除,並執行撤銷操作的時候,就會用到
restore
操作。
好吧,我知道你在想什么:這個概念更深一層的含義是什么呢?我們通過實例來解答。
二、模型事件
首先我們來看看這個被稱為 模型事件(model events) 的技術。它的基本概念非常簡單:
- 在
EventServiceProvider
中你可以添加一個特定的事件監聽器,並綁定一個閉包函數 - 在閉包函數中,你不需要接觸模型代碼就可以添加新的行為
- 綁定操作必須放在類的
boot()
方法中
這是一個把創建 (created
) 用戶事件與閉包函數進行綁定的簡單示例。閉包的 $user
參數包含了指定用戶的實例:
- public function boot(DispatcherContract $events)
- {
- parent::boot($events);
- User::created(function($user)
- {
- // doing something here, after User creation...
- });
- }
正如你想象的,每一個模型都有這些方法,所以,如果你想為 saved
事件綁定一個操作的話,你必須:
- User::saved(function($user)
- {
- // doing something here, after User save operation (both create and update)...
- });
另外一個有趣的功能是可以通過預方法停止當前操作。事實上,你可能會用到下面的方法:
creating
updating
saving
restoring
deleting
如果你想退出操作的話,可以返回一個布爾類型的 false
值。
假設用戶郵箱以 @deniedprovider.com 結尾的話,我們就退出 create
操作,可以這么做:
- User::creating(function($user)
- {
- if(ends_with($user->email, '@deniedprovider.com'))
- {
- return false;
- }
- });
很明顯,對於 created
, updated
, saved
, restored
, 和 deleted
事件則不能這么做,這些事件已經發生了,不能返回。
三、模型事件的實例
首先我們來看看這個被稱為 模型事件(model events) 的技術。它的基本概念非常簡單:
- 在
EventServiceProvider
中你可以添加一個特定的事件監聽器,並綁定一個閉包函數 - 在閉包函數中,你不需要接觸模型代碼就可以添加新的行為
- 綁定操作必須放在類的
boot()
方法中
這是一個把創建 (created
) 用戶事件與閉包函數進行綁定的簡單示例。閉包的 $user
參數包含了指定用戶的實例:
- public function boot(DispatcherContract $events)
- {
- parent::boot($events);
- User::created(function($user)
- {
- // doing something here, after User creation...
- });
- }
正如你想象的,每一個模型都有這些方法,所以,如果你想為 saved
事件綁定一個操作的話,你必須:
- User::saved(function($user)
- {
- // doing something here, after User save operation (both create and update)...
- });
另外一個有趣的功能是可以通過預方法停止當前操作。事實上,你可能會用到下面的方法:
creating
updating
saving
restoring
deleting
如果你想退出操作的話,可以返回一個布爾類型的 false
值。
假設用戶郵箱以 @deniedprovider.com 結尾的話,我們就退出 create
操作,可以這么做:
- User::creating(function($user)
- {
- if(ends_with($user->email, '@deniedprovider.com'))
- {
- return false;
- }
- });
很明顯,對於 created
, updated
, saved
, restored
, 和 deleted
事件則不能這么做,這些事件已經發生了,不能返回。
四、模型觀察者
我同意,模型事件非常酷,然而,有時候,你需要一些更高級的東西。
當你使用 Laravel 的時候,你基本上就是在使用面向對象編程,你可能需要做一些與模型事件相同的事,那就是模型觀察者 — 一個模型事件的高級版本。
要使用它,你需要做的就是像下面這樣聲明一個新的類(可以放在一個叫做 observers
的專用文件夾中):
- class BookObserver {
- public function creating($book)
- {
- // I want to create the $book book, but first...
- }
- public function saving($book)
- {
- // I want to save the $book book, but first...
- }
- public function saved($book)
- {
- // I just saved the $book book, so....
- }
- }
然后在 EventServiceProvider
類的 boot()
方法中這樣注冊它:
- Book::observe(new BookObserver);
這個理的概念和前面都是相同的,沒什么新的東西。通過觀察者,你也可以使用前面模型事件中學到的每一個單獨的概念。你可以聲明任何你想要的方法,然后只需要使用事件標示符綁定一個特定的事件。因此,creating
事件是與 creating()
方法相關的,以此類推。
很明顯,你可以在前置方法中終止該操作,比如說 createing()
和 updating()
:
- class BookObserver {
- public function creating($book)
- {
- $somethingGoesWrong = true;
- if($somethingGoesWrong)
- {
- return false;
- }
- }
- }
好了,下面我們來看一些模型觀察者的例子。
五、模型觀察者的實例
首先,我們為你展示如何通過模型觀察者實現前面模式事件中的第一個例子。
在 app/Observers
文件夾中創建 WelcomeUserObserver.php
文件,並加入下面的代碼:
- <?php
- namespace App\Observers;
- class WelcomeUserObserver {
- public function created($user){
- Mail::send('emails.welcome', ['user' => $user], function($message) use ($user)
- {
- $message->to($user->email, $user->first_name . ' ' . $user->last_name)->subject('Welcome to My Awesome App, '.$user->first_name.'!');
- });
- }
- }
然后在 EventServiceProvider
的 boot()
方法中注冊該觀察者:
- /**
- * Register any other events for your application.
- *
- * @param \Illuminate\Contracts\Events\Dispatcher $events
- * @return void
- */
- public function boot(DispatcherContract $events)
- {
- parent::boot($events);
- User::observe(new WelcomeUserObserver);
- }
這樣就 OK 了!現在你的觀察者已經與模型關聯起來了。
下面我們假設另一種情況。圖書管理員對代碼提出了一些新的需求:
- 當添加一個新的作者的時候,每一個用戶都收到一條通知
- 每次添加/刪除作者的時候,都發送一封郵件
- 最后,每次刪除一本書的時候,圖書管理員都要知道數據庫中有多少作者是沒有與相關的圖書的
好了,下面我們就開始吧。我們需要三個單獨的類(請記住我們的單一職責原則):CustomerNewAuthorObserver
、LibrarianAuthorObserver
、AuthorsWithoutBooksObservers
。
注意:你可以按自己喜歡的方式命名這些類,我們這里只是選擇比較容易與所選行為關聯起來的名稱。
下面我們分別來創建三個類:
- <?php
- // file: app/Observers/CustomerNewAuthorObserver
- namespace App\Observers;
- class CustomerNewAuthorObserver {
- public function created($author)
- {
- }
- }
- <?php
- // file: app/Observers/LibrarianAuthorObserver
- namespace App\Observers;
- class LibrarianAuthorObserver {
- public function created($author)
- {
- }
- public function deleted($author)
- {
- }
- }
- <?php
- // file: app/Observers/AuthorsWithoutBooksObservers
- namespace App\Observers;
- class AuthorsWithoutBooksObservers {
- public function deleted($author)
- {
- }
- }
好了,現在應該添加一些邏輯了,首先為 CustomerNewAuthorObserver
添加:
- <?php
- // file: app/Observers/CustomerNewAuthorObserver
- namespace App\Observers;
- class CustomerNewAuthorObserver {
- public function created($author)
- {
- // getting all users...
- $users = \App\User::all();
- foreach($users as $user)
- {
- Mail::send('emails.created_author_customer', ['author' => $author], function($message) use ($user)
- {
- $message->to($user->email, $user->first_name . ' ' . $user->last_name)->subject('New Author Added!');
- });
- }
- }
- }
注意:我知道這是一種非常簡單粗暴的方法,這里只是為了實現上面的目的。實際情況中可以使用郵件隊列。
- <?php
- // file: app/Observers/LibrarianAuthorObserver
- namespace App\Observers;
- class LibrarianAuthorObserver {
- public function created($author) {
- Mail::send('emails.created_author_librarian', ['author' => $author], function($message) use ($author)
- {
- $message->to('librarian@awesomelibrary.com', 'The Librarian')->subject('New Author: ' . $author->first_name . ' ' . $author->last_name);
- });
- }
- public function deleted($author) {
- Mail::send('emails.deleted_author_librarian', ['author' => $author], function($message) use ($author)
- {
- $message->to('librarian@awesomelibrary.com', 'The Librarian')->subject('New Author: ' . $author->first_name . ' ' . $author->last_name);
- });
- }
- }
最后:
- <?php
- // file: app/Observers/AuthorsWithoutBooksObservers
- namespace App\Observers;
- class AuthorsWithoutBooksObservers {
- public function deleted($author) {
- $authorsWithoutBooks = \App\Author::has('books', '=', 0)->get();
- if(count($authorsWithoutBooks) > 0){
- Mail::send('emails.author_without_books_librarian', ['authorsWithoutBooks' => $authorsWithoutBooks], function($message)
- {
- $message->to('librarian@awesomelibrary.com', 'The Librarian')->subject('Authors without Books! A check is required!');
- });
- }
- }
- }
注意:就像前面提過的,我們假定你已經了解了 Laravel 發送郵件的基本知識,沒有的話可以到官網學習下相關知識。
到這里並沒有結束。你可以在大量的案例和場景中使用 Laravel 的模型事件和模型觀察者。舉個例子,假設你寫博客,你希望每次發布一篇新文章或者更新一篇原有文章的時候,都更新一些站點地圖,這時就可以用到觀察者。再比如,當添加新書的時候,記錄一些東西,也可以用到觀察者。
六、總結
好了,現在你已經可以處理任何形式的事件了,從最基本的到更高級的觀察者的概念。你只是在 Eloquent 方面增加了一些額外的知識:你掌握的越多,你就越容易了解如何創建出更復雜的應用。此外,我們也應當遵循一些原則。
很不錯,對嗎?然而,我們不應該在任何地方都使用事件和觀察者。有時候,它們並不是最優的選擇,你可以試試其他的工具。所以,要具體問題具體分析。好的技術也並不永遠都適用於所有情況。
好了,可以開始下一步的學習了。如果你願意的話也可以休息一下。我們本系列到這里就告一段落了。在后面的兩個系列中我們可能會學到更多高級的知識。