你是否听说过单一职责原则(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. User::created(function($user)
  5. {
  6. // doing something here, after User creation...
  7. });
  8. }

正如你想象的,每一个模型都有这些方法,所以,如果你想为 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. User::created(function($user)
  5. {
  6. // doing something here, after User creation...
  7. });
  8. }

正如你想象的,每一个模型都有这些方法,所以,如果你想为 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. public function creating($book)
  3. {
  4. // I want to create the $book book, but first...
  5. }
  6. public function saving($book)
  7. {
  8. // I want to save the $book book, but first...
  9. }
  10. public function saved($book)
  11. {
  12. // I just saved the $book book, so....
  13. }
  14. }

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

  1. Book::observe(new BookObserver);

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

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

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

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

五、模型观察者的实例

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

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

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

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

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

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

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

  • 当添加一个新的作者的时候,每一个用户都收到一条通知

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

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

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

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

  1. <?php
  2. // file: app/Observers/CustomerNewAuthorObserver
  3. namespace App\Observers;
  4. class CustomerNewAuthorObserver {
  5. public function created($author)
  6. {
  7. }
  8. }
  9. <?php
  10. // file: app/Observers/LibrarianAuthorObserver
  11. namespace App\Observers;
  12. class LibrarianAuthorObserver {
  13. public function created($author)
  14. {
  15. }
  16. public function deleted($author)
  17. {
  18. }
  19. }
  20. <?php
  21. // file: app/Observers/AuthorsWithoutBooksObservers
  22. namespace App\Observers;
  23. class AuthorsWithoutBooksObservers {
  24. public function deleted($author)
  25. {
  26. }
  27. }

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

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

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

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

最后:

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

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

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

六、总结

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

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

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

Laravel 的 Events(事件) 及 Observers(观察者)的更多相关文章

  1. ActiveX控件的Events事件

    http://labview360.com/article/info.asp?TID=10152&FID=165 Active X函式库 对使用LabVIEW作为开发环境的开发人员来说,如果能 ...

  2. Laravel Relationship Events

    Laravel Relationship Events is a package by Viacheslav Ostrovskiy that adds extra model relationship ...

  3. laravel队列,事件简单使用方法

    A.队列的使用 1.队列配置文件存储在 config/queue.php 根据自己的情况进行配置 2..env文件 QUEUE_DRIVER=database(根据个人情况配置,redis等) 3.创 ...

  4. Laravel 5.1 事件、事件监听的简单应用

    ​ 有时候当我们单纯的看 Laravel 手册的时候会有一些疑惑,比如说系统服务下的授权和事件,这些功能服务的应用场景是什么,其实如果没有经历过一定的开发经验有这些疑惑是很正常的事情,但是当我们在工作 ...

  5. Spring之事件监听(观察者模型)

    目录 Spring事件监听 一.事件监听案例 1.事件类 2.事件监听类 3.事件发布者 4.配置文件中注册 5.测试 二.Spring中事件监听分析 1. Spring中事件监听的结构 2. 核心角 ...

  6. Node.js:events事件模块

    Nodejs的大部分核心API都是基于异步事件驱动设计的,所有可以分发事件的对象都是EventEmitter类的实例. 大家知道,由于nodejs是单线程运行的,所以nodejs需要借助事件轮询,不断 ...

  7. events(事件): 基础1

    1    所有能触发事件的对象都是 EventEmitter 类的实例. 这些对象开放了一个 eventEmitter.on() 函数,允许将一个或多个函数绑定到会被对象触发的命名事件上. 事件名称通 ...

  8. Laravel 项目中事件控制的体会--综合应用 trait 多态

    1 我们想像有这样的需求 1.1 应用中有两个类.其一是 荣誉(Honour)其一是 档案(Archive)Honour 和 Arhcive 是多态关联.即拥有档案属性的不只荣誉类,还有更多的类去关联 ...

  9. Lind.DDD.Events事件总线~自动化注册

    回到目录 让大叔兴奋的自动化注册 对于领域事件之前说过,在程序启动时订阅(注册)一些事件处理程序,然后在程序的具体位置去发布(触发)它,这是传统的pub/sub模式的体现,当然也没有什么问题,为了让它 ...

随机推荐

  1. EF中GroupBy扩展方法的简单使用

    public ActionResult ShopInfo() { ViewBag.ShopList = ShopService.GetEntities(x => x.IsDelete == fa ...

  2. remove ubuntu lvm

    sudo vgdisplay sudo vgremove groupname

  3. 2018-2019-2 《网络对抗技术》Exp3 免杀原理与实践 Week5 20165233

    Exp3 免杀原理与实践 实验内容 一.基础问题回答 1.杀软是如何检测出恶意代码的? 基于特征码的检测:通过与自己软件中病毒的特征库比对来检测的. 启发式的软件检测:就是根据些片面特征去推断.通常是 ...

  4. leetcode231

    public class Solution { public bool IsPowerOfTwo(int n) { )) == && n > ); } } https://lee ...

  5. Delphi 变体数组 Dataset Locate 查找定位

    Format 函数 Delphi 支持“开参数”和动态数组,变体数组,使用时的语法类似 Delphi 中的集合:采用两个方括号把不同类型的变量括起来(这太方便了啊),也可以采用声明一个 TVarRec ...

  6. IE浏览器中的加载项怎么删除

    IE浏览器中的加载项是一些软件或者浏览器的功能控件,我们可以通过禁用.开启来控制是否使用某些加载项,同时可以将一些加载项删除. 比如当我们遇到了一些不好的加载项,想要将它删除,通过这篇经验,教大家怎么 ...

  7. chrome 常用插件下载安装

    可在google的应用商店进行下载:chrome://apps/ 但大多时间无法链接. 国内插件下载地址: http://www.cnplugins.com http://chromecj.com/ ...

  8. python 之编写登陆接口

    基础需求: 让用户输入用户名密码 认证成功后显示欢迎信息 输错三次后退出程序 升级需求: 可以支持多个用户登录 (提示,通过列表存多个账户信息) 用户3次认证失败后,退出程序,再次启动程序尝试登录时, ...

  9. WP8.1 控件默认字体颜色 配置文件位置

    C:\Program Files (x86)\Windows Phone Kits\8.1\Include\abi\Xaml\Design\generic.xaml 可在App.xaml文件中over ...

  10. kafka 修改partition,删除topic,查询offset

    修改分区个数: ./kafka-topics./kafka/<id_of_kafka> --alter --partitions 10 --topic test_topic 上面命令将te ...