Background

最近要用 Laravel 做一個 email 認證的 Flow, 所以來紀錄一下.

Create a new project

$ composer create-project laravel/laravel email

Create a Laravel auth

$ php artisan make:auth

Setting up .env File

這邊只在 .env 中設定, 有需要的話可以自行到config下設定, 我這邊自己的設定

  • DB: PostgreSQL
  • email: Gmail

.env

...

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE={{DB_NAME}}
DB_USERNAME={{DB_USER}}
DB_PASSWORD={{DB_PASSWORD}}

...

QUEUE_DRIVER=database

...

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME={{YOUR_GMAIL_ACCOUNT}}
MAIL_PASSWORD={{YOUR_GMAIL_PASSWORD}}
MAIL_ENCRYPTION=tls

...

除了設定email & DB 之外, 因為我們還有用到Queue, 所以也記得把Queue那欄改成 database.

Setting up the Tables

Users Tables

2014_10_12_000000_create_users_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->tinyInteger('verified')->default(0);
            $table->string('email_token')->nullable();
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

Adding Table for Queues

add the table for queued jobs and failed job.

$ php artisan queue:table
$ php artisan queue:failed-table

Migrating Tables

$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table
Migrating: 2017_05_16_073316_create_jobs_table
Migrated:  2017_05_16_073316_create_jobs_table
Migrating: 2017_05_16_073509_create_failed_jobs_table
Migrated:  2017_05_16_073509_create_failed_jobs_table

Create the Email Template

在view的資料夾下面開一個新的資料夾叫 email.

$ mkdir resources/views/email
$ vim resources/views/email/email.blade.php

resources/views/email/email.blade.php

<h1>Click the Link To Verify Your Email</h1>
Click the following link to verify your email
{{url('/verifyemail/'.$email_token)}}

Create the SendVerficationEmail Queue Job

$ php artisan make:job SendVerificationEmail

這時候你會發現 app 的資料夾下面多了一個 Jobs 的資料夾

SendVerificationEmail

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Log;

use Mail;
use App\Mail\EmailVerification;

class SendVerificationEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($user)
    {
        //
        $this->user = $user;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $data = ['email_token' => $this->user->email_token];

        Mail::send('email.email', $data, function($message) {
            //$message->sender('[email protected]');
            $message->subject('Laravel 5.4 mail by Queue');
            $message->to($this->user->email);
        });
    }
}

Update the Auth Registration Process

In user model

User.php

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'email_token'
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}
$ vim app/Http/Controllers/Auth/RegisterController.php

RegisterController.php

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use App\Jobs\SendVerificationEmail;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */

    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */

    public function __construct()
    {
        $this->middleware('guest');
    }

     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'email_token' => base64_encode($data['email'])
        ]);
    }

    /**
    * Handle a registration request for the application.
    *
    * @param \Illuminate\Http\Request $request
    * @return \Illuminate\Http\Response
    */
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();
        event(new Registered($user = $this->create($request->all())));
        dispatch(new SendVerificationEmail($user));
        return view('verification');
    }
    /**
    * Handle a registration request for the application.
    *
    * @param $token
    * @return \Illuminate\Http\Response
    */

    public function verify($token)
    {
        $user = User::where('email_token', $token)->first();
        $user->verified = 1;
        if($user->save()){
            return view('emailconfirm', ['user'=>$user]);
        }
    }
}

Edit some files in view

為了整個流程能比較順利. 所以編輯一些view檔 for 註冊使用.

emailconfirm.blade.php

@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Registration Confirmed</div>
<div class="panel-body">
Your Email is successfully verified. Click here to <a href="{{url('/login')}}">login</a>
</div>
</div>
</div>
</div>
</div>
@endsection

verification.blade.php

@extends('layouts.app')
@section('content')
<div class=”container”>
<div class=”row”>
<div class=”col-md-8 col-md-offset-2">
<div class=”panel panel-default”>
<div class=”panel-heading”>Registration</div>
<div class=”panel-body”>
You have successfully registered. An email is sent to you for verification.
</div>
</div>
</div>
</div>
</div>
@endsection

Edit routes

web.php

...

Route::get('/verifyemail/{token}', 'Auth\[email protected]');

...

Testing the Email Verification Process

$ php artisan queue:work

執行後就放著不要理它了, 他會等到有Queue進來時才會有動作.

Open the register page

打開你的預設首頁, 右上角有register的按鈕 點下去

接著就註冊看看唄! 正常的情況你會收到信! 然後你的queue:work那邊會出現以下訊息

$ php artisan queue:work
[2017-05-16 09:25:57] Processing: App\Jobs\SendVerificationEmail
[2017-05-16 09:26:00] Processed:  App\Jobs\SendVerificationEmail

補充

如果你把 terminal 關掉的話, 那 Queue 的功能當然也就停止了. 下面的指令可以讓Queue在linux背景執行. 當然你也可以搭配 Cron Job 使用.

nohup php artisan queue:work &

Reference