如何使用 Forever 搭配 Laravel Queue 寄送 Email?
傳統寄送 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 1GitHub Commit : 單元測試 : 由 sync 寄送信件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16use App\Services\MailService;
class MailServiceTest extends TestCase
{
/** @test */
public function 由Sync寄送信件()
{
/** arrange */
/** act */
App::call(MailService::class . '@mailBySync');
/** assert */
$this->assertTrue(true);
}
}
這裡沒做任何測試,只是透過 PHPUnit 啟動 sync 方式寄送信件。
設定使用 Gmail 寄信
.env 2 2GitHub Commit : 設定使用 Gmail 寄信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
26APP_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
6MAIL_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 3GitHub Commit : 由 sync 寄送信件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16namespace 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 4GitHub Commit : 單元測試 : 由 queue 寄送信件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16use App\Services\MailService;
class MailServiceTest extends TestCase
{
/** @test */
public function 由queue寄送信件()
{
/** arrange */
/** act */
App::call(MailService::class . '@mailByQueue');
/** assert */
$this->assertTrue(true);
}
}
這裡沒做任何測試,只是透過 PHPUnit 啟動 queue 方式寄送信件。
設定本機環境使用 Queue
.env 5 5GitHub Commit : 設定本機環境使用 queue1
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
27APP_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 6GitHub Commit : 設定測試環境使用 queue1
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 | oomusou@mac:~/MyProject$ php artisan queue:table oomusou@mac:~/MyProject$ php artisan migrate |
目前是將 queue 建立在資料庫,因此須建立 jobs
table。7 7GitHub 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 | oomusou@mac:~/$ npm install -g forever |
將 forever
全域安裝。
啟動 Forever
1 | oomusou@mac:~/$ 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 8GitHub Commit : 由 queue 寄送信件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16namespace 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 上找到。