相关文章推荐

傳統寄送 email 是採用同步的方式,也就是當你寄出一封信,必須等 email server 回應後,才可以繼續後續的程式動作,因此使用者會有明顯的等待時間;若能搭配 queue 機制,寄送 email 後,馬上以非同步的方式回到原來程式繼續執行,會有另外一個 process 去消耗 queue,負責寄送 email。

Motivation


Laravel 的 Mail::send() 是以同步方式寄送 email,會有明顯的等待時間;而 Mail:queue() 則是以非同步的方式寄送 email,速度非常快,不過必須搭配 queue 以及其他配套機制。

Version


PHP 7.0.8
Laravel 5.2.45

先以 Mail::send() 透過 Gmail 寄送信件,然後再改用 Mail::queue() 方式寄送信件。

單元測試 : 由 Sync 寄信


MailServiceTest.php 1 1 GitHub Commit : 單元測試 : 由 sync 寄送信件

tests/MailServiceTest.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use App\Services\MailService;

class MailServiceTest extends TestCase
{
/** @test */
public functionSync寄送信件()
{

/** arrange */

/** act */
App::call(MailService::class . '@mailBySync');

/** assert */
$this->assertTrue(true);
}
}

這裡沒做任何測試,只是透過 PHPUnit 啟動 sync 方式寄送信件。

設定使用 Gmail 寄信


.env 2 2 GitHub Commit : 設定使用 Gmail 寄信

.env
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
APP_ENV=local
APP_DEBUG=true
APP_KEY=base64:PABSMLELX37jW2jJdwm9Fk6LvUWupulQXAWDZcfA7xE=
APP_URL=http://localhost

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
#DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=[your gmail address]
MAIL_PASSWORD=[your password]
MAIL_ENCRYPTION=tls

21 行

1
2
3
4
5
6
MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=[your gmail address]
MAIL_PASSWORD=[your password]
MAIL_ENCRYPTION=tls

  • MAIL_HOST : 設定為 smtp.gmail.com
  • MAIL_PORT : 設定為 587
  • MAIL_USERNAME : 設定你的 Gmail 帳號,如 [email protected]
  • MAIL_PASSWORD : 設定你的 Gmail 密碼。
  • MAIL_ENCRYPTION : 設定為 tls
  • 由 Sync 寄信


    MailService.php 3 3 GitHub Commit : 由 sync 寄送信件

    tests/MailService.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    namespace App\Services;

    use Illuminate\Mail\Message;
    use Mail;

    class MailService
    {
    public function mailBySync()
    {

    Mail::send('welcome', [], function (Message $message) {
    $message->sender('[email protected]');
    $message->subject('Laravel 5.2 mail by Sync');
    $message->to('[email protected]');
    });
    }
    }

    使用 Mail::send() 寄送信件。

  • 第 1 個參數為 blade 檔案名稱。
  • 第 2 個參數為要傳給 blade 的陣列,若不傳任何資料,也要傳一個空陣列。
  • 第 3 個參數為 closure,主要是 Laravel 希望你將 Message 物件資料填滿。
  • 實際執行會發現, Mail::send() 會需要等 1 到 2 秒才會執行完,因為是同步,要等 smtp server 回應後才會繼續執行。

    單元測試 : 由 Queue 寄信


    MailServiceTest.php 4 4 GitHub Commit : 單元測試 : 由 queue 寄送信件

    tests/MailServiceTest.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    use App\Services\MailService;

    class MailServiceTest extends TestCase
    {
    /** @test */
    public functionqueue寄送信件()
    {

    /** arrange */

    /** act */
    App::call(MailService::class . '@mailByQueue');

    /** assert */
    $this->assertTrue(true);
    }
    }

    這裡沒做任何測試,只是透過 PHPUnit 啟動 queue 方式寄送信件。

    設定本機環境使用 Queue


    .env 5 5 GitHub Commit : 設定本機環境使用 queue

    .env
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    APP_ENV=local
    APP_DEBUG=true
    APP_KEY=base64:PABSMLELX37jW2jJdwm9Fk6LvUWupulQXAWDZcfA7xE=
    APP_URL=http://localhost

    DB_CONNECTION=sqlite
    DB_HOST=127.0.0.1
    DB_PORT=3306
    #DB_DATABASE=homestead
    DB_USERNAME=homestead
    DB_PASSWORD=secret

    CACHE_DRIVER=file
    SESSION_DRIVER=file
    #QUEUE_DRIVER=sync
    QUEUE_DRIVER=database

    REDIS_HOST=127.0.0.1
    REDIS_PASSWORD=null
    REDIS_PORT=6379

    MAIL_DRIVER=smtp
    MAIL_HOST=smtp.gmail.com
    MAIL_PORT=587
    MAIL_USERNAME=[your gmail address]
    MAIL_PASSWORD=[your password]
    MAIL_ENCRYPTION=tls

    15 行

    1
    2
    #QUEUE_DRIVER=sync
    QUEUE_DRIVER=database

    QUEUE_DRIVER sync 改成 database

    設定測試環境使用 Queue


    phpunit.xml 6 6 GitHub Commit : 設定測試環境使用 queue

    phpunit.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <?xml version="1.0" encoding="UTF-8"?>
    <phpunit backupGlobals="false"
    backupStaticAttributes="false"
    bootstrap="bootstrap/autoload.php"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false">

    <testsuites>
    <testsuite name="Application Test Suite">
    <directory suffix="Test.php">./tests</directory>
    </testsuite>
    </testsuites>
    <filter>
    <whitelist processUncoveredFilesFromWhitelist="true">
    <directory suffix=".php">./app</directory>
    <exclude>
    <file>./app/Http/routes.php</file>
    </exclude>
    </whitelist>
    </filter>
    <php>
    <env name="APP_ENV" value="testing"/>
    <env name="QUEUE_DRIVER" value="database"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    </php>
    </phpunit>

    24 行

    1
    2
    3
    4
    5
    <php>
    <env name="APP_ENV" value="testing"/>
    <env name="QUEUE_DRIVER" value="database"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    </php>

  • CACHE_DRIVER SESSION_DRIVER 部分刪除。
  • QUEUE_DRIVER 改成 database
  • DB_CONNECTION 改成 sqlite
  • CACHE_DRIVER SESSION_DRIVER 必須刪除,否則無法再測試環境使用非同步方式寄送 email。

    儘管我們之前已經在 .env QUEUE_DRIVER 改成 database ,但 phpunit.xml QUEUE_DRIVER sync 設定會覆蓋掉 .env 的設定,所以這邊也要改成 database

    不能如一般單元測試的 DB_CONNECTION 設定成 sqlite_testing ,也就是將資料庫設定成 :memory: ,因為這種方式是使用 SQLite in Memory,只要測試一執行完,SQLite in Memory 就會被刪除,因此無法被 Forever 使用,因而無法使用非同步方式寄送 email。

    建立 jobs table


    1
    [email protected]:~/MyProject$ php artisan queue:table
    [email protected]:~/MyProject$ php artisan migrate

    目前是將 queue 建立在資料庫,因此須建立 jobs table。 7 7 GitHub Commit : 建立 jobs table

    安裝 Forever


    在 Laravel 的 Queues 文件中,是建議大家使用 Supervisor ,它會讓 php artisan queue:listen 在背景執行,持續地消耗 queue,不過 Supervisor 是 Linux 的程式,無法在 Windows 與 macOS 執行,因此對於開發並不方便。

    這裡介紹的是由 Node.js 開發的 Forever ,功能與 Supervisor 完全一樣,但因為由 Node.js 所開發,在 Windows、 macOS 與 Linux 都可執行,只要能能成功安裝 Node.js 即可。

    1
    [email protected]:~/$ npm install -g forever

    forever 全域安裝。

    啟動 Forever


    1
    [email protected]:~/$ forever start -l /Users/oomusou/Code/Laravel/Laravel52QueueForever/forever.log -c php artisan queue:work
  • start : 使用 forever 啟動其他服務。
  • -l : 指定 log 位置, Forever 預設將 log 放在 ~/.forever 目錄下,且每次啟動為亂數,若你想將 log 指定在特定目錄,並使用特定檔名,則必須使用 -l ,且必須使用完整路徑。
  • -c : 要啟動的 CLI 命令,使用 php artisan queue:work 執行 queue worker。
  • 由 Queue 寄信


    MailService.php 8 8 GitHub Commit : 由 queue 寄送信件

    tests/MailService.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    namespace App\Services;

    use Illuminate\Mail\Message;
    use Mail;

    class MailService
    {
    public function mailByQueue()
    {

    Mail::queue('welcome', [], function (Message $message) {
    $message->sender('[email protected]');
    $message->subject('Laravel 5.2 mail by Queue');
    $message->to('[email protected]');
    });
    }
    }

    使用 Mail::queue() 寄送信件,其他參數與 Mail::send() 完全相同。

    實際執行發現, Mail::queue() 會馬上執行完,不會有任何等待,因為是非同步,不用等 smtp server 回應就可繼續執行。

    Conclusion


  • 由同步改成非同步方式寄信,在程式方面,只要將 Mail::send() 改成 Mail::queue() 即可。
  • Laravel 支援多種 queue,本文使用最簡單的資料庫方式。
  • Forever 為 Node.js 所開發,在 Windows、macOS 與 Linux 都可使用,非常方便。
  • Sample Code


    完整的範例可以在我的 GitHub 上找到。

    Reference


    Taylor Otwell, Mail
    Taylor Otwell, Queues
    foreverjs, forever

     
    推荐文章