zoukankan      html  css  js  c++  java
  • Laravel 的 Events(事件) 及 Observers(观察者)


    你是否听说过单一职责原则(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 操作是与 createupdate 相关的。我们假设你需要添加一个行为,应用程序是创建一条新的记录还是更新一条已有的记录。难道对相同的事情还要声明两次吗?只需一个普通的 save 操作即可。
    • Restroe:当你的某个模型用到了软删除,并执行撤销操作的时候,就会用到 restore 操作。

    好吧,我知道你在想什么:这个概念更深一层的含义是什么呢?我们通过实例来解答。


    二、模型事件

    首先我们来看看这个被称为 模型事件(model events) 的技术。它的基本概念非常简单:

    • EventServiceProvider 中你可以添加一个特定的事件监听器,并绑定一个闭包函数
    • 在闭包函数中,你不需要接触模型代码就可以添加新的行为
    • 绑定操作必须放在类的 boot() 方法中

    这是一个把创建 (created) 用户事件与闭包函数进行绑定的简单示例。闭包的 $user 参数包含了指定用户的实例:

    1. public function boot(DispatcherContract $events)
    2. {
    3. parent::boot($events);
    4.  
    5. User::created(function($user)
    6. {
    7. // doing something here, after User creation...
    8. });
    9. }

    正如你想象的,每一个模型都有这些方法,所以,如果你想为 saved 事件绑定一个操作的话,你必须:

    1. User::saved(function($user)
    2. {
    3. // doing something here, after User save operation (both create and update)...
    4. });

    另外一个有趣的功能是可以通过预方法停止当前操作。事实上,你可能会用到下面的方法:

    • creating
    • updating
    • saving
    • restoring
    • deleting

    如果你想退出操作的话,可以返回一个布尔类型的 false 值。

    假设用户邮箱以 @deniedprovider.com 结尾的话,我们就退出 create 操作,可以这么做:

    1. User::creating(function($user)
    2. {
    3. if(ends_with($user->email, '@deniedprovider.com'))
    4. {
    5. return false;
    6. }
    7. });

    很明显,对于 created, updated, saved, restored, 和 deleted 事件则不能这么做,这些事件已经发生了,不能返回。


    三、模型事件的实例

    首先我们来看看这个被称为 模型事件(model events) 的技术。它的基本概念非常简单:

    • EventServiceProvider 中你可以添加一个特定的事件监听器,并绑定一个闭包函数
    • 在闭包函数中,你不需要接触模型代码就可以添加新的行为
    • 绑定操作必须放在类的 boot() 方法中

    这是一个把创建 (created) 用户事件与闭包函数进行绑定的简单示例。闭包的 $user 参数包含了指定用户的实例:

    1. public function boot(DispatcherContract $events)
    2. {
    3. parent::boot($events);
    4.  
    5. User::created(function($user)
    6. {
    7. // doing something here, after User creation...
    8. });
    9. }

    正如你想象的,每一个模型都有这些方法,所以,如果你想为 saved 事件绑定一个操作的话,你必须:

    1. User::saved(function($user)
    2. {
    3. // doing something here, after User save operation (both create and update)...
    4. });

    另外一个有趣的功能是可以通过预方法停止当前操作。事实上,你可能会用到下面的方法:

    • creating
    • updating
    • saving
    • restoring
    • deleting

    如果你想退出操作的话,可以返回一个布尔类型的 false 值。

    假设用户邮箱以 @deniedprovider.com 结尾的话,我们就退出 create 操作,可以这么做:

    1. User::creating(function($user)
    2. {
    3. if(ends_with($user->email, '@deniedprovider.com'))
    4. {
    5. return false;
    6. }
    7. });

    很明显,对于 created, updated, saved, restored, 和 deleted 事件则不能这么做,这些事件已经发生了,不能返回。


    四、模型观察者

    我同意,模型事件非常酷,然而,有时候,你需要一些更高级的东西。

    当你使用 Laravel 的时候,你基本上就是在使用面向对象编程,你可能需要做一些与模型事件相同的事,那就是模型观察者 — 一个模型事件的高级版本。

    要使用它,你需要做的就是像下面这样声明一个新的类(可以放在一个叫做 observers 的专用文件夹中):

    1. class BookObserver {
    2.  
    3. public function creating($book)
    4. {
    5. // I want to create the $book book, but first...
    6. }
    7.  
    8. public function saving($book)
    9. {
    10. // I want to save the $book book, but first...
    11. }
    12.  
    13. public function saved($book)
    14. {
    15. // I just saved the $book book, so....
    16. }
    17.  
    18. }

    然后在 EventServiceProvider 类的 boot() 方法中这样注册它:

    1. Book::observe(new BookObserver);

    这个理的概念和前面都是相同的,没什么新的东西。通过观察者,你也可以使用前面模型事件中学到的每一个单独的概念。你可以声明任何你想要的方法,然后只需要使用事件标示符绑定一个特定的事件。因此,creating 事件是与 creating() 方法相关的,以此类推。

    很明显,你可以在前置方法中终止该操作,比如说 createing()updating()

    1. class BookObserver {
    2.  
    3. public function creating($book)
    4. {
    5. $somethingGoesWrong = true;
    6.  
    7. if($somethingGoesWrong)
    8. {
    9. return false;
    10. }
    11. }
    12.  
    13. }

    好了,下面我们来看一些模型观察者的例子。


    五、模型观察者的实例

    首先,我们为你展示如何通过模型观察者实现前面模式事件中的第一个例子。

    app/Observers 文件夹中创建 WelcomeUserObserver.php 文件,并加入下面的代码:

    1. <?php
    2.  
    3. namespace AppObservers;
    4.  
    5. class WelcomeUserObserver {
    6.  
    7. public function created($user){
    8.  
    9. Mail::send('emails.welcome', ['user' => $user], function($message) use ($user)
    10. {
    11. $message->to($user->email, $user->first_name . ' ' . $user->last_name)->subject('Welcome to My Awesome App, '.$user->first_name.'!');
    12. });
    13.  
    14. }
    15.  
    16. }

    然后在 EventServiceProviderboot() 方法中注册该观察者:

    1. /**
    2. * Register any other events for your application.
    3. *
    4. * @param IlluminateContractsEventsDispatcher $events
    5. * @return void
    6. */
    7. public function boot(DispatcherContract $events)
    8. {
    9. parent::boot($events);
    10.  
    11. User::observe(new WelcomeUserObserver);
    12. }

    这样就 OK 了!现在你的观察者已经与模型关联起来了。

    下面我们假设另一种情况。图书管理员对代码提出了一些新的需求:

    • 当添加一个新的作者的时候,每一个用户都收到一条通知
    • 每次添加/删除作者的时候,都发送一封邮件
    • 最后,每次删除一本书的时候,图书管理员都要知道数据库中有多少作者是没有与相关的图书的

    好了,下面我们就开始吧。我们需要三个单独的类(请记住我们的单一职责原则):CustomerNewAuthorObserverLibrarianAuthorObserverAuthorsWithoutBooksObservers

    注意:你可以按自己喜欢的方式命名这些类,我们这里只是选择比较容易与所选行为关联起来的名称。

    下面我们分别来创建三个类:

    1. <?php
    2.  
    3. // file: app/Observers/CustomerNewAuthorObserver
    4.  
    5. namespace AppObservers;
    6.  
    7. class CustomerNewAuthorObserver {
    8.  
    9. public function created($author)
    10. {
    11.  
    12. }
    13.  
    14. }
    15.  
    16. <?php
    17.  
    18. // file: app/Observers/LibrarianAuthorObserver
    19.  
    20. namespace AppObservers;
    21.  
    22. class LibrarianAuthorObserver {
    23.  
    24. public function created($author)
    25. {
    26.  
    27. }
    28.  
    29. public function deleted($author)
    30. {
    31.  
    32. }
    33.  
    34. }
    35.  
    36. <?php
    37.  
    38. // file: app/Observers/AuthorsWithoutBooksObservers
    39.  
    40. namespace AppObservers;
    41.  
    42. class AuthorsWithoutBooksObservers {
    43.  
    44. public function deleted($author)
    45. {
    46.  
    47. }
    48.  
    49. }

    好了,现在应该添加一些逻辑了,首先为 CustomerNewAuthorObserver 添加:

    1. <?php
    2.  
    3. // file: app/Observers/CustomerNewAuthorObserver
    4.  
    5. namespace AppObservers;
    6.  
    7. class CustomerNewAuthorObserver {
    8.  
    9. public function created($author)
    10. {
    11. // getting all users...
    12. $users = AppUser::all();
    13.  
    14. foreach($users as $user)
    15. {
    16. Mail::send('emails.created_author_customer', ['author' => $author], function($message) use ($user)
    17. {
    18. $message->to($user->email, $user->first_name . ' ' . $user->last_name)->subject('New Author Added!');
    19. });
    20. }
    21. }
    22.  
    23. }

    注意:我知道这是一种非常简单粗暴的方法,这里只是为了实现上面的目的。实际情况中可以使用邮件队列

    1. <?php
    2.  
    3. // file: app/Observers/LibrarianAuthorObserver
    4.  
    5. namespace AppObservers;
    6.  
    7. class LibrarianAuthorObserver {
    8.  
    9. public function created($author) {
    10. Mail::send('emails.created_author_librarian', ['author' => $author], function($message) use ($author)
    11. {
    12. $message->to('librarian@awesomelibrary.com', 'The Librarian')->subject('New Author: ' . $author->first_name . ' ' . $author->last_name);
    13. });
    14. }
    15.  
    16. public function deleted($author) {
    17. Mail::send('emails.deleted_author_librarian', ['author' => $author], function($message) use ($author)
    18. {
    19. $message->to('librarian@awesomelibrary.com', 'The Librarian')->subject('New Author: ' . $author->first_name . ' ' . $author->last_name);
    20. });
    21. }
    22.  
    23. }

    最后:

    1. <?php
    2.  
    3. // file: app/Observers/AuthorsWithoutBooksObservers
    4.  
    5. namespace AppObservers;
    6.  
    7. class AuthorsWithoutBooksObservers {
    8.  
    9. public function deleted($author) {
    10. $authorsWithoutBooks = AppAuthor::has('books', '=', 0)->get();
    11.  
    12. if(count($authorsWithoutBooks) > 0){
    13. Mail::send('emails.author_without_books_librarian', ['authorsWithoutBooks' => $authorsWithoutBooks], function($message)
    14. {
    15. $message->to('librarian@awesomelibrary.com', 'The Librarian')->subject('Authors without Books! A check is required!');
    16. });
    17. }
    18. }
    19.  
    20. }

    注意:就像前面提过的,我们假定你已经了解了 Laravel 发送邮件的基本知识,没有的话可以到官网学习下相关知识。

    到这里并没有结束。你可以在大量的案例和场景中使用 Laravel 的模型事件和模型观察者。举个例子,假设你写博客,你希望每次发布一篇新文章或者更新一篇原有文章的时候,都更新一些站点地图,这时就可以用到观察者。再比如,当添加新书的时候,记录一些东西,也可以用到观察者。


    六、总结

    好了,现在你已经可以处理任何形式的事件了,从最基本的到更高级的观察者的概念。你只是在 Eloquent 方面增加了一些额外的知识:你掌握的越多,你就越容易了解如何创建出更复杂的应用。此外,我们也应当遵循一些原则。

    很不错,对吗?然而,我们不应该在任何地方都使用事件和观察者。有时候,它们并不是最优的选择,你可以试试其他的工具。所以,要具体问题具体分析。好的技术也并不永远都适用于所有情况。

    好了,可以开始下一步的学习了。如果你愿意的话也可以休息一下。我们本系列到这里就告一段落了。在后面的两个系列中我们可能会学到更多高级的知识。

  • 相关阅读:
    Redis与Memcached汇总
    那些年掉进的坑之AngularJS篇
    常见的Regex表达式(更新RFC标准的email检验)
    让IE6 IE7 IE8 IE9 IE10 IE11支持Bootstrap的解决方法
    Windows + Android + Cordova + ionic环境搭建
    less语法
    AngularJS 指令之 ng-style
    AngularJS 指令之 ng-if
    ionic之自定义 ion-tabs 图标
    AngularJS之延迟加载html template
  • 原文地址:https://www.cnblogs.com/mouseleo/p/8668001.html
Copyright © 2011-2022 走看看