はじめに
この記事はプログラミング初学者による備忘録用の記事であり、また、少しでも他の初学者のお役に立てればと思い書いています。
今回はlaravelでパスワード再設定メールをカスタムしてみたので、まとめてみました。
間違いなどがございましたら、ご指摘のほどよろしくお願い致します。
この記事の流れ
まずはじめに、どのようにLaravelのパスワード再設定メールの送信が行われているのかを各クラスの説明も含めてざっくりと解説します。その後に実際にカスタムしていきたいと思います。
結論だけ知りたい方は、実装の箇所まで飛んでください。
Laravelのパスワード再設定メールの送信がどのように行われるのか
パスワード再設定メールの送信は、Userモデルがインポートしているトレイトのメソッドである、sendPasswordResetNotificationメソッド
で行われています。
従って、独自の通知(パスワード再設定メール)を作りたい場合、Userクラス(モデル)
にsendPasswordResetNotificationメソッド
を定義して、オーバーライドすることで可能になります。
ちなみに、sendPasswordResetNotificationメソッド
は、Userクラスが継承している親Userクラス(Illuminate\Foundation\Auth\User)が利用しているCanResetPasswordトレイト
の中に定義されています。
親User.phpは下記のようになっています。
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
//略
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use CanResetPassword, //略
}
CanResetPassword.phpは下記のようになっています。
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
trait CanResetPassword
{
/**
* Get the e-mail address where password reset links are sent.
*
* @return string
*/
public function getEmailForPasswordReset()
{
return $this->email;
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
//ここにsendPasswordResetNotificationメソッドがあります
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}
話が少し逸れましたが、要するに、
独自のパスワード再設定メールを送信したい場合、UserモデルでsendPasswordResetNotificationメソッドをオーバーライドする必要があります。
オーバーライドする為には、下記手順を踏む必要があります。
-
Mailableクラス
を継承しているクラスを作成する。Mailableクラス
を使うことでテキストメールの送信が可能になるので、継承しているクラスを作成する。 -
Mailableクラス
を継承しているクラスを用いたメール送信メソッドを作成する為に、Notificationクラス
を作成する。 -
Notificationクラス
でmailableクラス
のメソッドを用いた(Mailableインスタンスを返す必要がある)メール送信メソッドを作成し、メールの詳細を設定する -
UserモデルでsendPasswordResetNotificationメソッドを使い、作成した
Notificationクラス
をインスタンス化することで、オーバライドする。
何故、オーバーライドすることで、パスワード再設定メールをカスタムできるかについては、パスワードリセット周りの実装のコードを読むと分かります。
下記リンク先にて先人が詳細を述べていますので、参考にしつつコードを辿ってみてください。
上記の手順説明で登場した、mailableクラス
、Notificationクラス
について詳しく見ていきたいと思います。
Mailableクラスとは
Laravelでは、Mailableクラスを使うことで、アプリケーションがテキストメールを送信する
ことを可能にします。
ちなみに、もう一つlaravelにはメールを取り扱うクラスが存在しており、MailMessageクラスと言います。このクラスはHTMLメール
を送ることが可能です。今回は汎用性が高いテキストメールを作りたいと思います。
Mailableクラスは、vendor/laravel/framework/src/Illuminate/Mailディレクトリ
に存在しています。基本的にMailableクラスは継承して扱いますが、アプリケーションに最初からMailableクラスを継承しているクラスは存在していません。
従って、make:mail Artisanコマンド
を使用して作成する必要があります。コマンド入力後、app/Mailディレクトリが作成され、下記のようなMailableクラスを継承しているクラスが作成されます。
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class TestMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('view.name');
}
}
Mailableクラスを継承したクラス内で様々なメソッドを呼び出すことができる
Mailableクラスを継承しているクラスでメールの詳細設定を行う場合、buildメソッド
の中で行います。このメソッドの中で、Mailableクラスのfrom、subject、view、textなどさまざまなメソッドを呼び出し、メールの詳細を設定します。
単純なメール送信などを行う場合は、Notificationクラスを使わずに、Mailableクラスを継承しているクラスのbuildメソッド内でメールの詳細を設定することがベストプラクティスかなと思っています。(設定が複雑でない為実装しやすい)
Mailableクラスの各メソッドの詳細はリンク先をお読み下さい。
Notificationクラスとは
ドキュメントの説明が一番分かりやすいので引用しておきます。
Laravelの各通知は、(通常、app/Notificationsディレクトリに設置される)クラスにより表されます。このディレクトリがアプリケーションで見つからなくても、心配ありません。make:notification Artisanコマンドを実行すると、作成されます。
このコマンドにより、真新しい通知クラスが、app/Notificationsディレクトリに生成されます。各通知クラスはviaメソッドと、特定のチャンネルに最適化したメッセージへ変換する、いくつかのメッセージ構築メソッド(toMail、toDatabaseなど)を含んでいます。
引用元:
laravel 6.x 通知
Notificationクラスを用いて通知を送信することができる
Laravelには、通知を送信する方法が主に2つあり、Notifiableトレイトのnotifyメソッド
かNotificationファサード
を使う方法です。
両者共に作成したNotificationクラスをインスタンス化して扱う点は同じです。
今回のパスワード再設定メール送信のカスタムでは、既存のパスワード再設定メール送信の処理でnotifyメソッド
が使われているので、Notifiableトレイトのnotifyメソッドを選択することでオーバーライドできるようにします。
では、実際に先ほど述べた手順でカスタムしていきたいと思います。
1.Mailableクラスを継承したクラスを作成する
まずは、mailableクラスを継承しているクラスを作成する為に、下記コマンドを実行します。
$ php artisan make:mail TestMail(クラス名)
実行することで、下記のような/app/Mail/TestMail.phpが作成されます。
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class TestMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('view.name'); //これを削除
return $this; //追記します
}
}
ここで重要な点は、
Mailableクラス
を継承したクラス(TestMailクラス)を作成した理由です。
後に登場するNotificationクラスのメソッド(toMailメソッド
)で、Mailableクラスのメソッドを使いテキストメールの詳細設定を行うには、NotificationクラスでMailableクラスのインスタンスを返す必要があります。
このような理由から、今回のMailableクラスを継承しているクラスは、NotificationクラスでMailableクラスのメソッドを使う為に作成したので、メールの詳細はbuildメソッドで設定しません。
従って、buildメソッドでは空設定のメールを返すように変更します。
2.Notificationクラスを作成後、Mailableクラスのメソッドを使いNotificationクラスのメソッド内でメールの詳細を設定する
まずはNotificationクラスを作成する為に、下記コマンドを実行します。
$ php artisan make:notification PasswordResetNotification.php
実行することで、下記のような/app/Notifications/PasswordResetNotification.php
が作成されます。(変更点をコメント表記で書いてあります)
<?php
namespace App\Notifications;
//追加
use App\Mail\TestMail;
//追加
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class PasswordResetNotification extends Notification
{
use Queueable;
//追加
public $token;
public $mail;
//追加
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(string $token, TestMail $mail) //この行を変更
{
//追加
$this->token = $token;
$this->mail = $mail;
//追加
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
//削除
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
//削除
//追加
return $this->mail
->from(config('mail.from.address'), config('mail.from.name'))
->to($notifiable->email)
->subject('[memo]パスワード再設定')
->text('emails.password_reset')
->with([
'url' => route('password.reset', [
'token' => $this->token,
'email' => $notifiable->email,
]),
'count' => config(
'auth.passwords.' .
config('auth.defaults.passwords') .
'.expire'
),
]);
//追加
}
//略
}
ここで重要な点は、
route(password.email)で渡ってくる$tokenと、Mailableクラスを継承したクラスをコンストラクタの引数で受け取り、作成したプロパティに代入する点です。
まず、route(password.email)で渡ってくる$token
をコンストラクタで受け取る理由は、後のtoMailメソッド(メールの詳細を決めるメソッド)のroute(password.reset)で、route(password.email)から渡ってきた$token
を使うことになるので、予め渡ってくる$token
をNotificationクラスのプロパティ($token)
に代入する為にコンストラクタで受け取り、プロパティ に代入します。
Mailableクラスを継承したクラスをコンストラクタで受け取る理由は、toMailメソッドでMailableクラスをインスタンス化する必要がある
為、予めコンストラクタの引数でclassの型宣言をすることでMailableクラスを継承したクラスのインスタンス
をコンストラクタにてDIするようにしています。
追記したコードの解説
public $token;
public $mail;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(string $token, TestMail $mail)
{
$this->token = $token;
$this->mail = $mail;
}
先ほど述べたように、プロパティを新規作成、コンストラクタでroute(password.email)で渡ってくる$token
とTestMailクラスのインスタンス
を引数として設定し、新規作成したプロパティに代入しています。
public function via($notifiable)
{
return ['mail'];
}
viaメソッドで通知方法を選択するのですが、 今回は、メールという1つの通知方法しかないので決め打ちします。
public function toMail($notifiable)
{
return $this->mail
->from(config('mail.from.address'), config('mail.from.name'))
->to($notifiable->email)
->subject('[syokumane]パスワード再設定')
->text('emails.password_reset')
->with([
'url' => route('password.reset', [
'token' => $this->token,
'email' => $notifiable->email,
]),
'count' => config(
'auth.passwords.' .
config('auth.defaults.passwords') .
'.expire'
),
]);
}
toMailメソッドで、メールの詳細を設定します。簡単に見ていきたいと思います。
return $this->mail
先ほど述べたように、NotificationクラスのtoMailメソッドでは、Mailableクラスのインスタンスを返す必要があるので、コンストラクタで作成した$this->mailを持ってきます。
from(config('mail.from.address'), config('mail.from.name'))
Mailableクラスが提供しているfromメソッド
といいます。
config関数を使うことで、config/mail.php
以下の値を取得しています。
アプリケーションで同じfromアドレスを全メールで使用する場合、グローバルなfromアドレスをconfig/mail.php設定ファイルで設定するべきだと思います。
下記のように、.envファイルの環境変数を使って設定します。
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
to($notifiable->email)
toメソッド
には、送信先メールアドレスを渡します。
$notifiable
には、パスワード再設定メール送信先となるUserクラス(モデル)のインスタンスが代入されています。
従って、$notifiable->email
で、パスワード再設定メールの送信先であるユーザーのメールアドレスを取得しています。
何故、$notifiable
にUserクラスのインスタンスが入っているのか説明すると
Userクラス(モデル)でNotifiableトレイト
をuseしているからです。
つまり、送信先にしたいクラスでNotifiableトレイトをuseするだけで、$notifiableを送信先として指定したクラスのインスタンスとして使えるようになります。
すごい。
subject(‘[password_reset]パスワード再設定')
subjectメソッド
には、メールの件名を渡します。
text('emails.password_reset')
textメソッド
は、テキスト形式のメールを送る場合に使うメソッドです。
引数で、メールのテンプレートディレクトリ先を指定します。
text('emails.password_reset')とすることで、resources/views/emailsディレクトリのpassword_reset.blade.php
がテンプレートとして使用されます。
->with([
'url' => route('password.reset', [
'token' => $this->token,
'email' => $notifiable->email
]),
'count' => config(
'auth.passwords.' .
config('auth.defaults.passwords') .
'.expire'
),
]);
テンプレートとなるBladeに渡す変数を、withメソッド
に連想配列形式で渡します。
'url' => route('password.reset', [
'token' => $this->token,
'email' => $notifiable->email
]),
keyであるurl
の値には、route関数を使ってpassword.resetのルーティングをセットします。
emailはpassword.resetのルーティングに記述されていませんが、クエリストリング
としてURLに追加されます。
URLは下記のようになります。
http://localhost/password/reset/(トークン)?email=(メールアドレス)
クエリストリングに関しては、下記サイトをご覧ください。
クエリ文字列 【query string】 クエリストリング
'count' => config(
'auth.passwords.' .
config('auth.defaults.passwords') .
'.expire'
),
keyであるcount
の値には、パスワード設定画面へのURLの有効期限(分単位)がセットされます。
config/auth.php以下を見てみると、デフォルトで60分となっています。
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
テキストメールのテンプレート書き方
例
下記のURLからパスワードの再設定を行って下さい。
{{ $url }}
URLの有効期間は{{ $count }}分です。
上記のように、withメソッドで作成した変数を使い、URLを作成、有効期間をユーザーに知らせるように設定します。
3.UserクラスでsendPasswordResetNotificationメソッドをUserモデルに定義してオーバーライドする
public function sendPasswordResetNotification($token)
{
$this->notify(new PasswordResetNotification($token, new BareMail()));
}
先ほど作成した通知クラスのインスタンスを生成し、notifyメソッドに渡します。
これでカスタムしたテキストメールがパスワード再設定メールとして送信されるようになります。
おわりに
パスワード再設定メールの処理を読み解くだけでもかなりのコード量でした。
既存のコードを読むことはとても参考になるので、今後も継続していきたいと思います。
次はLaravel側でのメール送信を「キュー」と呼ばれる仕組みを使った「非同期処理」が行えるらしいので調べてみたいと思います。
最後までお読み頂きありがとうございました。