傳統寄送 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 { public function 由Sync寄送信件() {
App::call(MailService::class . '@mailBySync');
$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:
DB_CONNECTION=sqlite DB_HOST=127.0.0.1 DB_PORT=3306
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 { public function 由queue寄送信件() {
App::call(MailService::class . '@mailByQueue');
$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:
DB_CONNECTION=sqlite DB_HOST=127.0.0.1 DB_PORT=3306
DB_USERNAME=homestead DB_PASSWORD=secret
CACHE_DRIVER=file SESSION_DRIVER=file
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 行
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
目前是將 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 即可。
將
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