LaravelのMail送信処理を深堀りする！


ある日Laravelでメール送信の実装をしようと思った時のこと、Laravelの公式ドキュメントを見て簡単に実装できることを知りました（以下のような書き方で実装可能です）。

```php
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:Mail.php
<?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】ファサードとは？何が便利か？どういう仕組みか？](https://qiita.com/minato-naka/items/095f2a1beec1d09f423e)がかなりわかりやすいのでおすすめです。

ということで、Mailファサードのメソッドは実質Mailerクラスの動的メソッドであるため、ここからは`\Illuminate\Mail\Mailer`を見ていきます。
※ Mail::fake()とするとMailFakeクラスが使えるのですが、このクラスはテストの時に使用するものなので、一旦今回は割愛します。

## Mailerクラスについて
ファイルの内容については、かなり多いので`Mail::send(/*引数省略*/);`に出てくる`send`からみていこうと思います。
ちなみに`Mail::send(/*引数省略*/);`について、引数を省略しないと以下のような実装になります。

```php
Mail::send('emails.test_content', [
            "test" => "テストメッセージ"
        ], function($message) {
            $message
                ->to('test@example.com')
                ->subject("メールのタイトル");
        });
```

#### Mailerクラスの送信処理
それでは、Mailerクラスのsendメソッドを見ていきましょう。
処理全体はこんな感じです。
Mailableクラスを使った送信処理と、送信以外の処理はコメントアウトで何をしているか書いてみました。

```php:Mailer.php
<?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つですね。

```php:Mailer.php
$this->sendSwiftMessage($swiftMessage);
$this->dispatchSentEvent($message, $data);
```
今回のメール送信では、上の`sendSwiftMessage`にてメールが送信されるます。
`dispatchSentEvent`については、queueを使用したとき処理されるため、今回は特に何も起きません。
そのため今回は`sendSwiftMessage`のみ扱います。

#### Mailer.phpのメール送信処理の詳細
sendSwiftMessageMailerクラスのメソッドで詳細はこのようになっています。

```php:Mailer.php
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メソッドの内容は以下の通りです。

```php:Swift_Mailer.php
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メソッドを見ていきましょう。

```php:SesTransport.php
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メソッドが使用されます。
それでは見ていきましょう。

冒頭でも記載しましたが、以下のようなコードでメールを送信することができます。

```php
// $mailableはMailableクラスを継承したクラスのインスタンスです
// $userにはメールアドレスやメールアドレスが格納されている配列が入ります。
Mail::to($user)->send($mailable);
```

まずは`Mail::to($user)`の部分から見ていきます。

```php:Mailer.php
public function to($users)
{
    return (new PendingMail($this))->to($users);
}
```
PendingMailのインスタンスを生成し、PendingMailのtoメソッドの返り値を返しています。

ちなみにPendingMailのtoメソッドは以下の通りで、$userをメンバー変数に格納してPendingMailのインスタンスを返しているだけです。

```php:PendingMail.php
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`のインスタンスを返しています。

```php:PendingMail.php
public function send(MailableContract $mailable)
{
    $this->mailer->send($this->fill($mailable));
}
```
Mailer.phpのsendの処理の冒頭3行でMailableを使用したときの送信処理が書かれていました。

```Mailer.php
if ($view instanceof MailableContract) {
    return $this->sendMailable($view);
}
```
sendMailableメソッドは次のように実装されています。

```php:Mailer.php
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メソッドに行き着きます！（ここに来て省略で申し訳ないですが笑）

## おわり
ということで深堀りはこれで以上です。
ライブラリは中身を知らなくても利用できてしまいますが、深堀りをするとより使いこなせますし、何よりも勉強になりました。
あまりライブラリやフレームワークの中身を読んだことない方はぜひ読んでみてください！

最後まで読んでくださりありがとうございました！！