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核心代码学习里,欢迎访问阅读。

相关文章
相关标签/搜索