CakePHP 4 で PHPMailer を利用する

CakePHP 4 で PHPMailer を使うときにちょっとハマったので動作するコードをメモしておきます。
今回はさくらインターネットのスタンダードプランでの内容ですが、違う環境でも応用できると思います。

# 環境
- さくらインターネット・スタンダードプラン
- CakePHP 4.1.7
- PHPMailer 公式：https://github.com/PHPMailer/PHPMailer

# コード

## composer.json
以下を追加して composer update します。

```json
    "require": {
        "phpmailer/phpmailer": "6.*",
    }
```

## 設定値
`app/config/app_local.php` に各種設定値を書きます。
パスワード以外は `app.php` でもいいかなと思いますが、今回は `app_local.php` に書いています。

```php
    'PHPMailer' => [
        'host' => '[さくらインターネットの初期ドメイン名]',
        'port' => 587,
        'username' => '[送信用に作成したメールアドレス]',
        'password' => '[作成したメールアドレスのパスワード]',
    ],
```

## メール送信
Controller に書いてもいいですし、Lib など作ってラッパークラスを書いてもいいと思います。
僕は後者を選択し、 `app/src/Lib/Mail.php` を作成しました。

まず以下を use します。

```php
use Cake\Core\Configure;
use Cake\View\View;
use Exception;
use PHPMailer\PHPMailer\PHPMailer;
```

以下でメール送信します。

```php
        try {
            $mailer = new PHPMailer(true);
            $mailer->CharSet = 'UTF-8';

            // 設定
            $mailer->isSMTP();
            $mailer->Host = Configure::read("PHPMailer.host");
            $mailer->Port = Configure::read("PHPMailer.port");
            $mailer->SMTPAuth = true;  // SMTP authentication の有効化
            $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;  // TLS で暗号化

            // 認証情報
            $mailer->Username = Configure::read("PHPMailer.username");
            $mailer->Password = Configure::read("PHPMailer.password");

            // from-to
            $mailer->setFrom($fromEmail, $fromName);
            $mailer->addAddress($toEmail);

            // 送信内容
            $mailer->isHTML($mimeType == 'text/html');
            $mailer->Subject = $signature . $subject;
            $mailer->Body = $content;  // あらかじめ template から展開

            return $mailer->send();
        } catch (Exception $e) {
            echo "Message could not be sent. Mailer Error: {$mailer->ErrorInfo}";
        }

        return false;
```

## テンプレート指定と展開
body はあらかじめ template を展開した文字列を渡しました。
一例ですが、実際に動いている実装例です。

```php
use Cake\View\View;

    /**
     * メール送信templateの展開
     *
     * template ファイルパス：app/templates/email
     *
     * @param string $templateName メールテンプレートファイル名
     * @param array $options メール送信に必要な情報配列
     *
     * @return string メールテンプレートを展開した文字列
     */
    private function expansionEmailTemplate(string $templateName, array $options = []): string
    {
        $view = new View();
        foreach ($options as $key => $value) {
            $view->set($key, $value);
        }

        return $view->render('email/' . $templateName, false);
    }
```

# デバッグ方法
send する前のどこでもいいので、以下を追加することで、画面でデバッグできます。

```php
use PHPMailer\PHPMailer\SMTP;

$mailer->SMTPDebug = SMTP::DEBUG_SERVER;
```

## Failed to connect to server 
ポート番号を間違えている場合など、何らかの理由で connection 確立に失敗した場合に出るエラーです。
例えば、TLS を指定しているのにポート番号が 25 だと失敗します。

```
2020-12-23 09:15:59 SMTP ERROR: Failed to connect to server: Connection timed out (110)
SMTP connect() failed. https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting
```

## メール送信に成功するデバッグ例
connection 確立から切断までの流れをデバッグできます。
僕の場合は認証がうまくいかず、ポート番号や TLS 暗号化設定など確認しながら実施しました。

```
2020-12-23 09:06:40 SERVER -> CLIENT: 220 www****.sakura.ne.jp ESMTP Sendmail 8.15.2/8.15.2; Wed, 23 Dec 2020 18:06:40 +0900 (JST)
2020-12-23 09:06:40 CLIENT -> SERVER: EHLO localhost.localdomain
2020-12-23 09:06:40 SERVER -> CLIENT: 250-www****.sakura.ne.jp Hello ****.ne.jp [ip address], p...
2020-12-23 09:06:40 CLIENT -> SERVER: STARTTLS
2020-12-23 09:06:40 SERVER -> CLIENT: 220 2.0.0 Ready to start TLS
2020-12-23 09:06:40 CLIENT -> SERVER: EHLO localhost.localdomain
2020-12-23 09:06:40 SERVER -> CLIENT: 250-www****.sakura.ne.jp Hello ....
2020-12-23 09:06:40 CLIENT -> SERVER: AUTH CRAM-MD5
2020-12-23 09:06:40 SERVER -> CLIENT: 334 ...
2020-12-23 09:06:40 CLIENT -> SERVER: [credentials hidden]
2020-12-23 09:06:40 SERVER -> CLIENT: 235 2.0.0 OK Authenticated
2020-12-23 09:06:40 CLIENT -> SERVER: MAIL FROM:<no-reply@example.com>
2020-12-23 09:06:40 SERVER -> CLIENT: 250 2.1.0 <no-reply@example.com>... Sender ok
2020-12-23 09:06:40 CLIENT -> SERVER: RCPT TO:<mamy1326@example.jp>
2020-12-23 09:06:40 SERVER -> CLIENT: 250 2.1.5 <mamy1326@example.jp>... Recipient ok
2020-12-23 09:06:40 CLIENT -> SERVER: DATA
2020-12-23 09:06:40 SERVER -> CLIENT: 354 Enter mail, end with "." on a line by itself
2020-12-23 09:06:40 CLIENT -> SERVER: Date: Wed, 23 Dec 2020 18:06:39 +0900
2020-12-23 09:06:40 CLIENT -> SERVER: To: mamy1326@example.jp
2020-12-23 09:06:40 CLIENT -> SERVER: From: =?UTF-8?B....==?= <no-reply@example.com>
2020-12-23 09:06:40 CLIENT -> SERVER: Subject: =?UTF-8?....?=
2020-12-23 09:06:40 CLIENT -> SERVER: =?UTF-8?....?=
2020-12-23 09:06:40 CLIENT -> SERVER: Message-ID: <....@localhost.localdomain>
2020-12-23 09:06:40 CLIENT -> SERVER: X-Mailer: PHPMailer 6.2.0 (https://github.com/PHPMailer/PHPMailer)
2020-12-23 09:06:40 CLIENT -> SERVER: MIME-Version: 1.0
2020-12-23 09:06:40 CLIENT -> SERVER: Content-Type: text/plain; charset=UTF-8
2020-12-23 09:06:40 CLIENT -> SERVER: Content-Transfer-Encoding: 8bit
2020-12-23 09:06:40 CLIENT -> SERVER:
2020-12-23 09:06:40 CLIENT -> SERVER: [メール本文が表示されます]
2020-12-23 09:06:40 CLIENT -> SERVER:
2020-12-23 09:06:40 CLIENT -> SERVER: .
2020-12-23 09:06:40 SERVER -> CLIENT: 250 2.0.0 0BN96eLQ033862 Message accepted for delivery
2020-12-23 09:06:40 CLIENT -> SERVER: QUIT
2020-12-23 09:06:40 SERVER -> CLIENT: 221 2.0.0 www****.sakura.ne.jp closing connection
```

# 備考
各種設定値や PHPMailer で指定する値などは、利用するメールサーバーによって異なるので、正確な情報を得て反映してください。