七、创建短消息系统

在这一章中,我们将介绍一个先进的时事通讯系统,它将使用 Laravel 的queueemail库。在本节之后,我们将学习如何设置和触发排队任务,以及如何解析电子邮件模板并向订阅者发送大量电子邮件。本章涵盖的主题有:

  • 创建数据库并迁移订户的表
  • 创建订户模型
  • 创建我们的订阅表单
  • 验证和处理表单
  • 创建队列系统来处理电子邮件
  • 使用电子邮件类处理队列中的电子邮件
  • 测试系统
  • 直接用队列发送电子邮件

在本章中,我们将使用第三方服务,这将需要访问您的脚本,因此在继续之前,请确保您的项目在线可用。

创建数据库并迁移订阅者表

在成功安装 Laravel 4 和从app/config/database.php定义数据库凭证后,创建一个名为chapter7的数据库。

创建数据库后,打开终端并浏览项目文件夹,然后运行以下命令:

php artisan migrate:make create_subscribers_table --table=subscribers –-create

前面的命令将为我们生成一个名为subscribers的新 MySQL 迁移。现在导航到app/database/中的migrations文件夹,打开前面命令刚刚创建的迁移文件,并更改其内容,如下代码所示:

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSubscribersTable extends Migration {

  /**
   * Run the migrations.
   *
   * @return void
   */
  public function up()
  {
    Schema::create('subscribers', function(Blueprint $table)
    {
      $table->increments('id');
      $table->string('email,100)->default('');
      $table->timestamps();
    });
  }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('subscribers');
  }
}

对于本章,我们将只需要email列,它将保存订阅者的电子邮件地址。我将此列设置为最多 100 个字符长,具有数据类型VARCHAR,,并且不会保留为空。

保存文件后,运行以下命令执行迁移:

php artisan migrate

如果没有出现错误,您就可以开始项目的下一步了。

创建订户模型

为了让从雄辩的 ORM 中受益,最好的实践是创建一个模型。

将以下代码保存在app/models/subscribers.php中:

<?php
Class Subscribers Extends Eloquent{
  protected $table = 'subscribers';
  protected $fillable = array('email');
}

我们用变量$table设置表名,用户必须填写值的列用$fillable变量设置。现在我们的模型已经准备好了,我们可以进行下一步,开始创建我们的控制器和表单。

创建我们的订阅表单

现在我们应该创建一个表单将记录保存到数据库并指定其属性。

  1. First, open your terminal and type the following command:

    php php artisan controller:make SubscribersController

    该命令将在app/controllers目录中为您生成一个带有一些空白方法的SubscribersController.php文件。

    artisan命令生成的默认控制器方法不是 RESTful。

  2. Now, open up app/routes.php and add the following code:

    php //We define a RESTful controller and all its via route//directly Route::controller('subscribers', 'SubscribersController');

    我们可以用一行代码定义控制器上声明的所有动作,而不是逐个定义所有动作。如果你的方法名可以直接作为get或者post动作使用,使用controller()方法可以节省很多时间。第一个参数为控制器设置 URI ( 统一资源标识符),第二个参数定义将访问和定义控制器文件夹中的哪个类。

    这样设置的控制器是自动 RESTful 的。

  3. Now, let's create the form's controller. Remove all methods inside the auto-generated class and add the following code in your controller file:

    php //The method to show the form to add a new feed public function getIndex() { //We load a view directly and return it to be served return View::make('subscribe_form'); }

    首先,我们定义了流程。这里相当简单;我们将该方法命名为getCreate() ,因为我们希望我们的Create方法是 RESTful 的。我们只是加载了一个视图文件,我们将在下一步中直接生成它。

  4. Now let's create our view file. In this example, I've used the Ajax POST technique using jQuery. Save this file as subscribe_form.blade.php at app/views/:

    ```php <!doctype html> <!doctype html>

    Subscribe to Newsletter /Some Little Minor CSS to tidy up the form/ body{margin:0;font-family:Arial,Tahoma,sans-serif;text-align:center;padding-top:60px;color:#666;font-size:24px} input{font-size:18px} input[type=text]{width:300px} div.content{padding-top:24px;font-weight:700;font-size:24px} .success{color:#0b0} .error{color:#b00}
    {{-- Form Starts Here --}}
    {{Form::open(array('url'=> URL::to('subscribers/submit'),'method' => 'post'))}}
    <p>Simple Newsletter Subscription</p>
    {{Form::text('email',null,array('placeholder'=>'Type your E-mail address here'))}}
    {{Form::submit('Submit!')}}
    
    {{Form::close()}}
    {{-- Form Ends Here --}}
    
    {{-- This div will show the ajax response --}}
    <div class="content"></div>
    {{-- Because it'll be sent over AJAX, We add thejQuery source --}}
    {{ HTML::script('http://code.jquery.com/jquery-1.8.3.min.js') }}
    <script type="text/javascript">
      //Even though it's on footer, I just like to make//sure that DOM is ready
      $(function(){
        //We hide de the result div on start
        $('div.content').hide();
        //This part is more jQuery Related. In short, we //make an Ajax post request and get the response//back from server
        $('input[type="submit"]').click(function(e){
          e.preventDefault();
          $.post('/subscribers/submit', {
            email: $('input[name="email"]').val()
          }, function($data){
            if($data=='1') {
              $('div.content').hide().removeClass('success error').addClass('success').html('You\'ve successfully subscribed to ournewsletter').fadeIn('fast');
            } else {
              //This part echos our form validation errors
              $('div.content').hide().removeClass('success error').addClass('error').html('There has been an error occurred:<br /><br />'+$data).fadeIn('fast');
            }
          });
        });
        //We prevented to submit by pressing enter or anyother way
        $('form').submit(function(e){
          e.preventDefault();
          $('input[type="submit"]').click();
        });
      });
    </script>
    

    ```

    前面的代码会产生一个简单的表单,如下图所示:

    Creating our subscription form

现在我们的表单已经准备好了,我们可以继续处理表单。

验证和处理表单

现在我们已经有了表单,我们需要验证并存储数据。我们还需要检查请求是否是 Ajax 请求。此外,我们需要用 Ajax 方法将成功的代码或错误消息返回到表单中,以便最终用户能够理解后端发生了什么。

app/controllers/保存SubscribersController.php内的数据:

//This method is to process the form
public function postSubmit() {

  //we check if it's really an AJAX request
  if(Request::ajax()) {

    $validation = Validator::make(Input::all(), array(
      //email field should be required, should be in an email//format, and should be unique
      'email' => 'required|email|unique:subscribers,email'
    )
    );

    if($validation->fails()) {
      return $validation->errors()->first();
    } else {

      $create = Subscribers::create(array(
        'email' => Input::get('email')
      ));

      //If successful, we will be returning the '1' so the form//understands it's successful
      //or if we encountered an unsuccessful creation attempt,return its info
      return $create?'1':'We could not save your address to oursystem, please try again later';
    }

  } else {
    return Redirect::to('subscribers');
  }
}

以下几点解释了前面的代码:

  1. 使用Request类的ajax()方法,您可以检查请求是否是 Ajax 请求。如果不是 Ajax 请求,我们将被重定向回订阅者的页面(表单本身)。
  2. 如果是有效请求,那么我们使用Validation类的make()方法运行表单。在这个例子中,我已经直接编写了规则,但是最好的做法是在模型中设置它们,并直接将它们调用到控制器。规则required检查字段是否已填写。规则email检查输入是否是有效的电子邮件格式,最后,规则unique帮助我们知道值是否已经在一行上。
  3. 如果表单验证失败,我们会直接返回第一条错误消息。返回的内容将是 Ajax 的响应,它将被回显到我们的表单页面中。因为错误消息是自动生成的有意义的文本消息,所以在我们的示例中直接使用它是安全的。此消息将显示我们验证中的所有错误。例如,如果字段不是有效的电子邮件地址,或者如果电子邮件已经提交到数据库,它将回显。
  4. 如果表单验证通过,我们尝试使用 Laravel 的雄辩表单的create()方法将电子邮件添加到我们的数据库中。

为基本电子邮件发送创建队列系统

Laravel 4 中的队列是这个框架最好的特性之一。想象一下你有一个漫长的过程,比如调整所有图像的大小,发送大量电子邮件,或者大量数据库操作。当你处理这些时,它们需要时间。那我们为什么要等呢?相反,我们将把这些进程放在队列中。使用 Laravel v4,这很容易管理。在本节中,我们将创建一个简单的队列并循环发送电子邮件,并尝试使用以下步骤向每个订户发送电子邮件:

  1. 首先,我们的项目需要一个队列驱动程序。这可能是亚马逊 SQSBeanstalkd或者铁 IO 。我选择 Iron IO 是因为,目前,它是唯一支持推送队列的队列驱动程序。然后我们需要从包装商那里拿到包裹。将"iron-io/iron_mq": "dev-master"添加到composer.jsonrequire键。应该是如下代码:

    php "require": { "laravel/framework": "4.0.*", "iron-io/iron_mq": "dev-master" },

  2. 现在您应该运行以下命令来更新/下载新包:

    ```php php composer.phar update

    ```

  3. 我们需要一个来自 Laravel 官方支持的队列服务之一的帐户。在这个例子中,我将使用免费的 Iron.io 服务。

    1. 首先,注册网站 http://iron.io
    2. 其次,登录后,创建一个名为laravel的项目。
    3. 然后,点击你的项目。有一个关键图标,它将为您提供项目的凭据。点击那个;它会为你提供project_idtoken
  4. Now navigate to app/config/queue.php, and change the default key driver to iron.

    在我们打开的queue文件中,有一个名为iron的密钥,您将使用它来填充凭据。在那里提供您的tokenproject_id信息,对于queue键,键入laravel

  5. 现在,打开你的终端,输入以下命令:

    ```php php artisan queue:subscribe laravel http://your-site-url/queue/push

    ```

  6. 如果一切顺利,您将获得如下输出:

    ```php Queue subscriber added: http://your-site-url/queue/push

    ```

  7. 现在,当您查看 Iron.io 项目页面上的队列选项卡时,您将看到一个由 Laravel 生成的新push队列。因为是推送队列,到时候队列会给我们打电话。

  8. Now we need some methods to catch the push request, to marshal it, and to fire it.

    1. First, we will need a get method to trigger the push queue (which will mimic the codes to trigger the queue).

      app文件夹的routes.php文件中添加以下代码:

      php //This code will trigger the push request Route::get('queue/process',function(){ Queue::push('SendEmail'); return 'Queue Processed Successfully!'; });

      这段代码将向一个名为SendEmail的类发出push请求,我们将在后面的步骤中创建这个类。

    2. Now we will need a listener to marshal the queue. Add the following code into your routes.php file in the app folder:

      php //When the push driver sends us back, we will have to //marshal and process the queue. Route::post('queue/push',function(){ return Queue::marshal(); });

      该代码将从我们的队列驱动程序获取push请求,该驱动程序将它放入队列并运行。

      我们需要一个类来激发队列和发送电子邮件,但是首先我们需要一个电子邮件模板。将代码保存为app/views/ emails/目录中的test.blade.php:

      php <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> </head> <body> <h2>Welcome to our newsletter</h2> <div>Hello {{$email}}, this is our test message fromour Awesome Laravel queue system.</div> /body> </html>

      这是一个简单的电子邮件模板,将包装我们的电子邮件。

    3. Now we need a class to fire up the queue and send the e-mail. Save these class files directly into the routes.php file in the app folder:

      ```php //When the queue is pushed and waiting to be marshalled, we should assign a Class to make the job done Class SendEmail {

           public function fire($job,$data) {
      
             //We first get the all data from our subscribers//database
             $subscribers = Subscribers::all();
      
             foreach ($subscribers as $each) {
      
               //Now we send an email to each subscriber
               Mail::send('emails.test',array('email'=>$each->email), function($message){
      
                 $message->from('us@oursite.com', 'Our Name');
      
                 $message->to($each->email);
      
               });
             }
      
             $job->delete();
           }
         }
      

      ```

      我们在前面的代码中编写的类SendEmail将覆盖我们将要分配的队列作业。方法fire() 是 Laravel 自己处理队列事件的方法。因此,当队列被封送时,方法fire()中的代码将被运行。当我们调用 Queue::push()方法时,我们也可以将参数作为第二个参数传递给job

      在 ORM 的帮助下,我们使用all()方法从数据库中获取了所有的订阅者方法,然后通过foreach循环,我们遍历了所有的记录。

      job处理成功后,在底部,我们使用delete()方法,这样在下一个队列调用时,作业就不会再次启动。

    在深入研究代码之前,我们必须学习 Laravel 4 新特性电子邮件类的基础知识。

使用电子邮件类处理队列中的电子邮件

在继续之前,我们需要确保我们的电子邮件凭证是正确的,并且我们已经正确设置了所有的值。打开app/config/目录下的mail.php文件,根据您的配置填写设置:

  • 参数驱动程序设置要使用的电子邮件驱动程序;mailsendmailsmtp是默认的邮件发送参数。
  • 如果您正在使用smtp,则需要根据您的提供商填写hostportencryptionusernamepassword字段。
  • 您也可以使用字段from设置默认的起始地址,这样您就不必一遍又一遍地输入相同的地址。
  • 如果您使用sendmail作为邮件发送驱动程序,您应该确保它在参数sendmail中的路径是正确的。否则,电子邮件将不会被发送。
  • 如果您仍在测试您的应用,或者您处于实时环境中,并且希望在没有发送错误/未完成的电子邮件风险的情况下测试您的更新,您应该将pretend设置为true,这样它就不会实际发送电子邮件,而是将它们保留在日志文件中供您调试。

当我们遍历所有记录时,我们使用了 Laravel 的新电子邮件发送器Mail类,它基于流行的组件Swiftmailer

Mail::send()方法采用三个主要参数:

  • 第一个参数是电子邮件将包装在其中的电子邮件模板文件的路径
  • 第二个参数是将发送到视图的变量
  • 第三个参数是关闭功能,我们可以设置标题fromtoCC/BCCattachments

此外,您还可以使用attach()方法向电子邮件添加附件

测试系统

在我们设置了队列系统和email类之后,我们准备测试我们编写的代码:

  1. 首先,确保数据库中有一些有效的电子邮件地址。
  2. 现在浏览你的浏览器,输入http://your-site-URL/queue/process
  3. 当您看到消息Queue Processed时,这意味着队列被成功发送到我们的队列驱动程序。我想一步一步地描述这里正在发生的事情:
    • 首先,我们用我们需要排队的参数和附加数据 ping 包含Queue::push()的队列驱动程序
    • 然后,在队列驱动程序得到我们的响应后,它将向我们的发布路线queue / push发出发布请求,这是我们之前使用queue:subscribe工匠命令设置的
    • 当我们的脚本从队列驱动程序接收到push请求时,它会封送并触发排队事件
    • 在它被触发之后,类内部的方法fire()运行并完成我们分配给它的任务
  4. 过一段时间,如果一切顺利,你会开始在收件箱里收到那些电子邮件。

直接用队列发送邮件

在某些电子邮件发送情况下,尤其是如果我们使用第三方 SMTP,并且发送用户注册、验证电子邮件等,队列调用可能不是最佳解决方案,但如果我们可以在发送电子邮件时直接将其排队,那就太好了。Laravel 的Email类也处理这个。不使用Mail::send(),如果使用相同参数的Mail::queue(),邮件发送会在队列驱动的帮助下完成,最终用户的响应时间会更快。

总结

在本章中,我们使用 Laravel 的Form Builder类和 jQuery 的 Ajax post 方法创建了一个简单的时事通讯订阅表单。我们已经验证并处理了表单,并将数据保存到数据库中。我们还学习了如何使用第三方队列驱动程序,通过 Laravel 的queue类轻松地对长进程进行排队。我们还介绍了使用 Laravel 4 发送电子邮件的基本知识。

在下一章中,我们将编写一个 Q & A 站点,该站点将具有分页系统、标签系统、第三方认证库、问答投票系统、选择最佳答案的选项以及问题搜索系统。