ある日Laravelでメール送信の実装をしようと思った時のこと、Laravelの公式ドキュメントを見て簡単に実装できることを知りました(以下のような書き方で実装可能です)。
Mail::send(/*引数省略*/); // Mailファサードのみで送信
Mail::to($user)->send($mailable); // Mailableクラスを使用してメールを送信
「すごい便利!」と思ったのですが、簡単すぎてなんかいまいち理解しきれていない。まあライブラリなので完全に理解する必要はないのですが、ちょっと気になるなともやもやが起きました笑
ということで、そんなもやもやを解消するために、Mailファサードについて深堀りをしようと思います。
同じもやもやを抱えてしまった方、一緒にもやもやを解消していきましょう!(「おっと、この部分は違うぞ!」と思った方はコメントで教えてください)
著者の環境
- Laravel 8.78.1
- PHP 8.0.8
- MailerはAmazon SESを使用
Mailファサードについて
まずはMailファサードについてみていきます。
Mailファサードのファイルは以下のようになっています。
<?php
namespace Illuminate\Support\Facades;
use Illuminate\Support\Testing\Fakes\MailFake;
/**
* @method static \Illuminate\Mail\PendingMail to($users)
* @method static \Illuminate\Mail\PendingMail bcc($users)
* @method static void raw(string $text, $callback)
* @method static void send(\Illuminate\Contracts\Mail\Mailable|string|array $view, array $data = [], \Closure|string $callback = null)
// 中略
*
* @see \Illuminate\Mail\Mailer
* @see \Illuminate\Support\Testing\Fakes\MailFake
*/
class Mail extends Facade
{
// 中略
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'mailer';
}
}
アノテーション(@method)がたくさん書いてありますね。
これらのメソッドはMailファサードが静的メソッドっぽく使うことができるものです。
例えばMail::send();
のような形で使うことができます。
facadeクラスを継承しているので、クラスには実装されていないメソッドが静的メソッドように使用することができるんですね。
実際には\Illuminate\Mail\Mailer
がnewされており、そのインスタンスのメソッドが実行されています。
この辺りの依存解決はIlluminate\Mail\MailServiceProvider
でなされています。
参考
ファサードについて詳しく知りたいとはという方は【Laravel】ファサードとは?何が便利か?どういう仕組みか?がかなりわかりやすいのでおすすめです。
ということで、Mailファサードのメソッドは実質Mailerクラスの動的メソッドであるため、ここからは\Illuminate\Mail\Mailer
を見ていきます。
※ Mail::fake()とするとMailFakeクラスが使えるのですが、このクラスはテストの時に使用するものなので、一旦今回は割愛します。
Mailerクラスについて
ファイルの内容については、かなり多いのでMail::send(/*引数省略*/);
に出てくるsend
からみていこうと思います。
ちなみにMail::send(/*引数省略*/);
について、引数を省略しないと以下のような実装になります。
Mail::send('emails.test_content', [
"test" => "テストメッセージ"
], function($message) {
$message
->to('test@example.com')
->subject("メールのタイトル");
});
Mailerクラスの送信処理
それでは、Mailerクラスのsendメソッドを見ていきましょう。
処理全体はこんな感じです。
Mailableクラスを使った送信処理と、送信以外の処理はコメントアウトで何をしているか書いてみました。
<?php
namespace Illuminate\Mail;
class Mailer implements MailerContract, MailQueueContract
{
* Send a new message using a view.
*
* @param \Illuminate\Contracts\Mail\Mailable|string|array $view
* @param array $data
* @param \Closure|string|null $callback
* @return void
*/
public function send($view, array $data = [], $callback = null)
{
// Mailableクラスを用いた時の送信処理
if ($view instanceof MailableContract) {
return $this->sendMailable($view);
}
// $viewの中身がstringかarrayか、arrayであったら連想配列かどうかによって、$view, $plain, $rawに格納する値を分けている。
[$view, $plain, $raw] = $this->parseView($view);
// Messageクラスのインスタンスを作成し、各変数に格納している。
$data['message'] = $message = $this->createMessage();
// 第3引数のコールバック関数を実行
$callback($message);
// メールのbody等を作成
$this->addContent($message, $view, $plain, $raw, $data);
// $messageにToのアドレスをセット
if (isset($this->to['address'])) {
$this->setGlobalToAndRemoveCcAndBcc($message);
}
// Swift_Messageのインスタンスをゲッターで取得
$swiftMessage = $message->getSwiftMessage();
if ($this->shouldSendMessage($swiftMessage, $data)) {
$this->sendSwiftMessage($swiftMessage);
$this->dispatchSentEvent($message, $data);
}
}
ということで、if文を含めた最後の3行で送信が行われています。
この部分を深堀りしてみます。
if文の条件であるshouldSendMessage($swiftMessage, $data)
についてですが、今回はtrue
になります。
(詳細は割愛します)
それではif文の中身を見ていきます。
メソッドは以下の2つですね。
$this->sendSwiftMessage($swiftMessage);
$this->dispatchSentEvent($message, $data);
今回のメール送信では、上のsendSwiftMessage
にてメールが送信されるます。
dispatchSentEvent
については、queueを使用したとき処理されるため、今回は特に何も起きません。
そのため今回はsendSwiftMessage
のみ扱います。
Mailer.phpのメール送信処理の詳細
sendSwiftMessageMailerクラスのメソッドで詳細はこのようになっています。
protected function sendSwiftMessage($message)
{
$this->failedRecipients = [];
try {
return $this->swift->send($message, $this->failedRecipients);
} finally {
$this->forceReconnection();
}
}
この時$this->swift
にはSwift_Mailerクラスのインスタンスが入っています。
Swift_Mailer
のsendメソッドでメールが送信されているんですね。
このSwift_Mailer
のsendメソッドの内容は以下の通りです。
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$sent = 0;
try {
$sent = $this->transport->send($message, $failedRecipients);
} catch (Swift_RfcComplianceException $e) {
foreach ($message->getTo() as $address => $name) {
$failedRecipients[] = $address;
}
}
return $sent;
}
sendメソッドの中にさらにsendメソッドがありました。
今回はMailerにAmazon SESを用いているので、$this->transport
にはSesTransport
のインスタンスが格納されています。
それでは、SesTransport
のsendメソッドを見ていきましょう。
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$result = $this->ses->sendRawEmail(
array_merge(
$this->options, [
'Source' => key($message->getSender() ?: $message->getFrom()),
'RawMessage' => [
'Data' => $message->toString(),
],
]
)
);
// 後略
}
ようやく、送信処理の最深部にたどり着きました!
このsendRawEmailメソッドによって、Amazon SESにメールの送信処理がリクエストされています($this->ses
にはSesClient
のインスタンスが入っています)。
※実際には、さらにSesClientのexecuteメソッド(AwsClientTraitにて実装)が処理されたりするのですが、キリがないので一旦ここを最深部とさせてください笑
ということで、Mailを送信する処理はかなり階層が深くなっていました。
次にMailableを使用する場合について、書いてきます。
Mailableを使用する場合について
Mailableを使用する場合についても、実はMailerクラスのsenderメソッドが使用されます。
それでは見ていきましょう。
冒頭でも記載しましたが、以下のようなコードでメールを送信することができます。
// $mailableはMailableクラスを継承したクラスのインスタンスです
// $userにはメールアドレスやメールアドレスが格納されている配列が入ります。
Mail::to($user)->send($mailable);
まずはMail::to($user)
の部分から見ていきます。
public function to($users)
{
return (new PendingMail($this))->to($users);
}
PendingMailのインスタンスを生成し、PendingMailのtoメソッドの返り値を返しています。
ちなみにPendingMailのtoメソッドは以下の通りで、$userをメンバー変数に格納してPendingMailのインスタンスを返しているだけです。
public function to($users)
{
$this->to = $users;
if (! $this->locale && $users instanceof HasLocalePreference) {
$this->locale($users->preferredLocale());
}
return $this;
}
ということで、Mail::to($user)->send($mailable);
はPendingMailのsendメソッドが実行されているのでした。
PendingMail
のsendメソッドは以下のようにMailer
のsendメソッドが使用されています。
※ $this->fill($mailable)
はMailable
のインスタンスを返しています。
public function send(MailableContract $mailable)
{
$this->mailer->send($this->fill($mailable));
}
Mailer.phpのsendの処理の冒頭3行でMailableを使用したときの送信処理が書かれていました。
if ($view instanceof MailableContract) {
return $this->sendMailable($view);
}
sendMailableメソッドは次のように実装されています。
protected function sendMailable(MailableContract $mailable)
{
return $mailable instanceof ShouldQueue
? $mailable->mailer($this->name)->queue($this->queue)
: $mailable->mailer($this->name)->send($this);
}
今回、Mailable
はShouldQueue
ではないので$mailable->mailer($this->name)->send($this);
が実行されます。
これをたどると、最終的に、Mailer
クラスのsendメソッドに行き着きます!(ここに来て省略で申し訳ないですが笑)
おわり
ということで深堀りはこれで以上です。
ライブラリは中身を知らなくても利用できてしまいますが、深堀りをするとより使いこなせますし、何よりも勉強になりました。
あまりライブラリやフレームワークの中身を読んだことない方はぜひ読んでみてください!
最後まで読んでくださりありがとうございました!!