この記事では Laravel のメール送信機能について紹介します。単にコードの断片を載せるだけだとまぁマニュアル(日本語)読めばいいって話になるので、簡単なサンプルコードを作成する流れで説明します。
Laravel のバージョンは 5.7 です。
サンプルの概要
今回はまず以下の通りユーザーがコメントを入力する機能を作成します。
そしてコメントを送信したタイミングでユーザー宛てにサンキューメールを送信する機能を実装します。
準備
コードの雛形生成
認証機能を自動生成。
$ php artisan make:auth
モデル、コントローラー、マイグレーションファイルを自動生成。
$ php artisan make:model Models/Comment -m -c
User.php を Models ディレクトリに移動
はじめ User.php
は app
ディレクトリの直下に置かれていますが、他にもモデルが増えた際に居心地が悪いのでモデルを入れるディレクトリを追加します。
Models
ディレクトリを新規作成して、User.php
をその中に移動します。
$ mkdir app/Models
$ mv app/User.php app/Models/User.php
User.php
は namespace を変更。
namespace App\Models;
次に config/auth.php
は70行目あたりの providers
の設定項目で、users
→ model
の値を変更します。
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
最後に app/Http/Controllers/Auth/RegisterController.php
を編集。冒頭の use
文を変更します。
use App\Models\User;
ルーティング
routes/web.php
を編集します。以下を追記してください。
Route::group(['middleware' => 'auth'], function () {
// 入力フォーム画面を返却するルート
Route::get('/comment', 'CommentController@showForm')->name('comment');
// 入力を受け付けるルート
Route::post('/comment', 'CommentController@create');
// 入力後にリダイレクトする完了画面のルート
Route::get('/comment/thanks', 'CommentController@thanks')->name('comment.thanks');
});
マイグレーション
マイグレーションファイルは最初に実行した make:model
コマンドで database/migrations
ディレクトリに雛形が作成されているので以下の通り中身を記述します。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCommentsTable extends Migration
{
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->text('body');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('comments');
}
}
記述できたらマイグレーションを実行します。
$ php artisan migrate
コントローラー
コントローラーも make:model
コマンドで app/Http/Controllers
ディレクトリに雛形が作成されています。以下の通り記述してください。
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class CommentController extends Controller
{
// 入力フォーム画面
public function showForm()
{
return view('comments.form');
}
// 入力を受け付ける
public function create(Request $request)
{
$user = Auth::user();
$comment = new Comment(['body' => $request->comment]);
$user->comments()->save($comment);
// TODO ここでメールを送る
return redirect()->route('comment.thanks');
}
// 入力後にリダイレクトする完了画面
public function thanks()
{
$comment = Auth::user()
->comments()
->orderBy('id', 'desc')
->first();
return view('comments.thanks', compact('comment'));
}
}
モデル
モデルも app/Models
ディレクトリに雛形ができているので内容を記述します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = [
'body',
];
}
User.php
にはリレーションを表現する以下のメソッドを追加します。
public function comments()
{
return $this->hasMany('App\Models\Comment');
}
テンプレート
今回は2つの画面を追加します。
入力フォーム画面
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<form method="POST" action="{{ route('comment') }}">
@csrf
<div class="form-group">
<label for="comment">コメントください</label>
<textarea class="form-control" name="comment" id="comment" required></textarea>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">送信</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
完了画面
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<p class="card-title">あなたのコメント</p>
<p class="card-text">{{ $comment->body }}</p>
</div>
</div>
</div>
</div>
</div>
@endsection
確認
ここまでできたらブラウザから確認しましょう。ユーザー登録して /comment
に遷移してください。コメント欄が表示されたでしょうか?エラーなくコメントが送信できたらOKです。
長くなってしまいましたが、準備はここまでです。以下から、メールを送信する機能を追加していきます。
メールを送る
送信設定
メールを送信するためにはまず SMTP サーバーの設定を行います。しかし開発中など、本番用の SMTP サーバーを用意できないこともあるでしょう(お金もかかりますしね)。
そこで今回は、テスト用のメール送信サービス Mailtrap を使います。まずこちらから登録してください。Google もしくは Github アカウントでログイン可能です。ログインすると、以下のような画面が表示されるでしょう。「Demo Inbox」が作成されています。
「Demo Inbox」をクリックすると SMTP の接続情報が確認できます(下の画像はユーザー名とパスワードをぼかしてあります)。
.env
に SMTP の接続情報を記載します。
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=xxxxxxxxxxxxxx
MAIL_PASSWORD=xxxxxxxxxxxxxx
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=sample@email.com
MAIL_FROM_NAME=サンプルアプリ
これで設定は完了です。メール関連の設定は config/mail.php
に記述されていますが、設定値は .env
を参照してるので編集するのは .env
だけでOKです。
Mailable クラスを作成する
送信されるメールの内容(題名、From、本文、添付)は、Mailable
クラスにまとめられます。
まずは以下の artisan コマンドで Mailable
クラスを生成します。
$ php artisan make:mail CommentPosted
Mailable
クラスの名前は、どんなときに送られるか?を表すものにするとよいようです。今回は「コメントが送られたとき」なので、CommentPosted
にしました。
さて app/Mail
ディレクトリに CommentPosted.php
の雛形が作成されていますね。内容は以下の通り記述します。
<?php
namespace App\Mail;
use App\Models\Comment;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class CommentPosted extends Mailable
{
use Queueable, SerializesModels;
public $user;
public $comment;
public function __construct(User $user, Comment $comment)
{
$this->user = $user;
$this->comment = $comment;
}
public function build()
{
return $this
->subject('コメントありがとうございます')
->view('emails.comments.posted');
}
}
Mailable
クラスには基本的に2つのメソッドを実装します。コンストラクタと build
メソッドです。
コンストラクタ
コンストラクタではメール本文中で表示したいデータを受け取って、プロパティに代入します。Mailable
クラスの public
プロパティは後に指定するテンプレート内で参照することができます。
build メソッド
ここでは Mailable
クラスが持つメソッドを組み合わせてメールの内容を作成します。よく使うメソッドを以下に紹介します。
メソッド | 用途 |
---|---|
from |
送信元のアドレスと送信名を指定する |
subject |
件名を指定する |
view |
本文のテンプレート名を指定する(HTML) |
text |
本文のテンプレート名を指定する(テキスト) |
今回のようにfrom
メソッドを省略した場合は config/mail.php
に設定されたデフォルトの from 情報が使われます。
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
メール用のテンプレート
メールにも Blade テンプレートを用います。
resources/emails/comments/posts.php
を作成してください。
<p>
{{ $user->name }} さん、<br>
コメントありがとうございます!
</p>
<p>
あなたのコメント:<br>
『{{ $comment->body }}』
</p>
<p>
や <br>
タグがあることからも分かるように、HTML メールが送信されます。
コントローラー
メールの送信は、Mail
ファサードが行います。
// 追加
use Illuminate\Support\Facades\Mail;
class CommentController extends Controller
{
// 中略
public function create(Request $request)
{
$user = Auth::user();
$comment = new Comment(['body' => $request->comment]);
$user->comments()->save($comment);
// 追加
Mail::to($user)->send(new CommentPosted($user, $comment));
return redirect()->route('comment.thanks');
}
// 中略
}
to
メソッドに宛先を渡し、さらに send
メソッドに Mailable
クラスを渡すとメールが送信されます。「誰に to(user)
」「何を send(Mailable)
」送るかという送信処理そのものを受け持つのが Mail
ファサードなのですね。
to
メソッドに宛先として渡せる値は何種類かあります。
- メールアドレス文字列
- オブジェクト
- オブジェクトのコレクション
オブジェクトまたはオブジェクトのコレクションが渡された場合は、email
プロパティが存在するものとみなされ、その値が宛先として使用されます。
確認
ここまでできたら再度コメントを送信してみてください。Mailtrap のダッシュボードから以下のようにメールが送られていることが確認できます。ちなみに本来の宛先には送信されません。
Mailtrap、実は今回初めて使ってみたのですが便利ですね!無料プランだと以下の制限がありますが、少なくとも個人で開発する分には手軽で です。
- 受信ボックスは1つ
- メールの保管は50通まで
- 受信するのは1秒間に2通まで
- メンバー追加不可
そして Laravel ではメールも非常に簡単なコードで送信できてしまいますね!
まとめ
メールを送るには、3つの登場人物が必要です。
登場人物 | 役割 |
---|---|
Mailable クラス |
送信物、題名、From、本文、添付など |
テンプレート | 具体的な本文の内容 |
Mail ファサード |
送信処理、誰に何を送るか |
実装の課題
簡単にメール送信は実装できたのですが、ここまでの実装方法には課題があります。
実際にコメント送信機能を動かしていただくと分かるかと思いますが、メール送信を追加する前と比べて明らかに体感できるほど動作が遅くなっています。コントローラーのメールを送る行をコメントアウトしたり戻したりしながら試してみるとよいです。メールの送信はアプリケーションにとって結構重たい処理なのですね。
ただこれには解決策があります。メール送信を非同期に処理すればよいのです。
上記のコントローラーのコードでは、時間のかかるメール送信が完了するまで待ってから画面を返していました(厳密にはリダイレクトですね)。「非同期に」というのは、メール送信が完了するのを待たずに画面を返してしまうということです。
ここからは、Laravel のキューの機能を使ってメールを非同期に送信する方法を紹介します。
メールを非同期に送信する
キューを使う
非同期処理を実現するには「キュー」という技術を使います。 以下は全体の流れを表した図です。
まずアプリケーションが非同期で実行したい処理の内容をキューに追加します。
この「処理の内容」をジョブと呼びます。具体的には、どのクラスのどのメソッドをどういう引数で実行するか、という情報が文字列にシリアライズされたデータです。
キューはジョブの入れ物です。リレーショナルデータベースや Redis などの NoSQL データストアが用いられます。
次に登場するのがワーカーです。ワーカーはキューを監視するプログラムです。一定間隔で常にキューに問い合わせをし続け、ジョブがあれば取り出して処理を実行します。
説明すると複雑な感じですが、Laravel ではフレームワークがほとんど面倒を見てくれているので、少ない記述でキューを使ったメールの非同期送信が実現できます。
データベース
前述の通り、キューはジョブが格納できれば RDB でも NoSQL でもいいのですが、今回は Laravel で手軽に導入できる RDB を使用する方法を紹介します。
まずは .env
に、キューとしてデータベースを使う設定を記述します。
QUEUE_CONNECTION=database
次にキュー用のテーブルを作成します。queue:table
コマンドでマイグレーションファイルを作成し、migrate
でデータベースに適用します。
$ php artisan queue:table
$ php artisan migrate
マイグレーションができたらデータベースを確認しましょう。jobs というテーブルが作成されているはずです。
コントローラー
そして送信する箇所のメソッドを send
から queue
に変更します。
Mail::to($user)->queue(new CommentPosted($user, $comment));
実装はたったこれだけです!
ワーカーを起動する
監視用のワーカープロセスを起動しましょう。
$ php artisan queue:work
コンソールはカーソルが戻ってこない状態になり、ワーカーが処理を開始したときと完了したときにログが表示されます。ワーカーのプロセスを中断するには ctrl+C
を入力してください。
確認
では再び画面から動作を確認しましょう。
いかがでしょうか?画面の応答が早くなったのではないでしょうか。Mailtrap を見るとメールが送られていることも分かりますね。
Supervisor
先ほど queue:work
コマンドでワーカーを起動しましたが、本番環境では Supervisor などのプロセス管理システムを利用してワーカーを制御します。Supervisor を使うと下記のメリットがあります。
- ワーカーをバックグラウンドで起動できる。
- ワーカーが処理に失敗したときに自動で再起動してくれる。
Supervisor の設定方法は OS によって異なるので、本記事ではオマケとして Mac でのインストール〜設定〜起動方法を紹介します。以降の手順を試す場合は、queue:work
コマンドは中断させておいてください。
まずは Homebrew で Supervisor をインストールします。
$ brew install supervisor
設定ファイルを作成します。任意の場所でいいのですが、今回は /usr/local/etc/supervisor
を設定ファイルの格納ディレクトリとします。conf.d
ディレクトリには Supervisor で管理するプログラムごとの設定ファイルを格納します。
$ mkdir -p /usr/local/etc/supervisor/conf.d
echo_supervisord_conf
コマンドでデフォルト状態の設定を作成します。
$ echo_supervisord_conf > /usr/local/etc/supervisor/supervisord.conf
$ vi /usr/local/etc/supervisor/supervisord.conf
最後の行のみ編集します。各プログラムの設定ファイルの場所ですね。
[include]
files = /usr/local/etc/supervisor/conf.d/*.conf
次にLaravel のワーカー用の設定ファイルを作成します。
$ vi /usr/local/etc/supervisor/conf.d/laravel-worker.conf
以下の内容を記述しましょう。
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /プロジェクトまでのパス/artisan queue:work
autostart=true
autorestart=true
user=実行ユーザー名(ログインユーザーで良いかと)
numprocs=3
redirect_stderr=true
stdout_logfile=/プロジェクトまでのパス/worker.log
Supervisor を起動して…
$ supervisord -c /usr/local/etc/supervisor/supervisord.conf
$ supervisorctl start laravel-worker:*
起動状態を確認すると、numprocs
で設定した通り3つプロセスが立ち上がっています。
$ supervisorctl status all
laravel-worker:laravel-worker_00 RUNNING pid 80202, uptime 0:04:08
laravel-worker:laravel-worker_01 RUNNING pid 80203, uptime 0:04:08
laravel-worker:laravel-worker_02 RUNNING pid 80204, uptime 0:04:08
ここまでで Supervisor の設定は完了です。前段と同様に動作することを確認しましょう。
ファイルを添付する
最後に、メールにファイルを添付する方法を紹介します。ファイル添付も複雑なコードは必要ありません。
ワーカーの再起動
ここで一点注意です。キューでメールを処理している場合は、ファイルに編集を加えたあとにワーカーを再起動させましょう。
// artisan コマンドを使っている場合
$ php artisan queue:restart
// Supervisor を使っている場合
$ supervisorctl restart laravel-worker:*
ワーカーはジョブを処理すると説明しました。そしてジョブの中身はどのクラスのどのメソッドをどういう引数で呼び出すかという情報でした。つまりクラスを呼び出すためにワーカーはアプリケーションのコピーを持っていると考えてください。
ワーカーは起動しっぱなしのプログラムなので、起動した時点の状態のアプリケーションのコピーを持っていて、コードの変更を反映させるためには再起動が必要なのです。
添付ファイル
さて、コメントをくれたユーザーには猫の GIF をプレゼントしましょう!今回はこちらの GIF (via GIPHY)を使います。まぁどれでもお気に入りのやつでいいんですけどね。
右クリックでダウンロードしたら、storage
ディレクトリに cat.gif
という名前で移動してください。
Mailable
クラスを編集します。
public function build()
{
return $this
->subject('コメントありがとうございます')
->view('emails.comments.posted')
->attach(storage_path('cat.gif'));
}
attach
メソッドを追加します。
これだけです
本文中に貼り付ける
次に、画像を本文中に貼り付ける方法を紹介します。
<p>
{{ $user->name }} さん、<br>
コメントありがとうございます!
</p>
<p>
あなたのコメント:<br>
『{{ $comment->body }}』
</p>
<p>
<img src="{{ $message->embed(storage_path('cat.gif')) }}">
</p>
メールの本文となるテンプレートでは、自動的に $message
という変数が使えるようになります。$message
から embed
メソッドを呼び出してください。引数はファイルのパスです。
画面から動作を確認しましょう。メール本文に GIF が表示されているでしょうか。
以上、Laravel のメール送信機能について紹介しました。パターンさえ分かれば簡単な記述でだいたいのことが実現できてしまう Laravel の魅力が伝われば幸いです