MUGIJIRU.JP

Webエンジニアの雑談ブログ

Laravel5.2のメール送信で複数の設定を使い分ける

Laravel5.2のメール送信でよく使われるのはMail::sendだと思っていますが、
特定の処理だけ別のsmtpサーバを介して送信をしたいときの設定方法がなかなか見つからなかったので
自分の試した実装を紹介します。

業務システム等では、たまに特定のアドレスに対してのみ別のsmtpサーバを介してメールを送信する要件があったりするわけですが、どこに手を入れたら良いものかとずいぶん悩みました。

まずLaravelで一般的なMail::send

ドキュメント等でも書かれている例ですが、単純にメールを送るだけならbladeテンプレートを利用して簡単に実装できます。

送信サーバ等の設定

.envで行います。

MAIL_DRIVER=sendmail
MAIL_HOST=localhost
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
実装
<?php
$view_script_path = 'mail.thanks';
$from = 'hoge@example.com';
$from_name = 'test送信者';
$subject = 'メールタイトル';

Mail::send(
    ['text' => $view_script_path], // テキストメール
    $data,
    function ($message) use ($from, $from_name, $to, $subject) {
        $message
            ->from($from, $from_name)
            ->to($to)
            ->subject($subject);
    }
);

設定を上書きするには?

ぱっと見、「ファサードかこの$messageでサーバ設定をオーバーライドできる関数が呼べるんだろうか。」と思うんですが、無さそうなんですよね。

ソースを一通り解析してみると、サーバ等の設定はServiceProviderのregisterの時点で固定されているようで、config::setでの変更もできないことがわかりました。
Transport設定の作り分けも相当奥までconfigが入り込んでいて、どうやらLaravel5.2のメール送信は一つの設定で実施するのが前提の思想でできているようです。

色々考えた結果

泥臭いですが、ServiceProviderとSwift_MailerのTransportを生成しているTransportManagerの仕事を自力でやるSwift_Mailerの入替えをやるのがまずは妥当かなと思い、関数を書いてみました。

※5/30 18:45 更新 (当記事を見た職場の方から、setSwiftMailerメソッドのアドバイスを頂いたお陰で内容が改善されました)

<?php
/**
 * メール送信関数
 *
 * Laravel5.2のMail::send のtransportの融通を利かせたバージョン
 *
 * sendmail, smtp以外に対応する場合はIlluminate\Mail\TransportManagerを参考に追加実装すること
 *
 * @param mixed    $view     Mail::sendの第1引数 ['text' => {viewスクリプトパス}] 等
 * @param array    $data     Mail::sendの第2引数 
 * @param callable $callback Mail::sendの第3引数
 * @param array    $override_config 特定の設定で送信する場合のみ指定  config('mail')を上書きするための情報
 * @param bool     $perpetuate      設定を永続化させるか true:永続化させる
 * 
 */
function send_mail($view, array $data, $callback, array $override_config = [], $perpetuate = false)
{
    if (!$override_config) {
        return \Mail::send($view, $data, $callback);
    }

    // Swift_Mailerの入替え処理クロージャ
    $replace_swift_mailer = function($config) {
        app()['config']['mail'] = $config;
        \Mail::setSwiftMailer(new Swift_Mailer((new \Illuminate\Mail\TransportManager(app()))->driver()));
    };

    // 現在のconfig
    $current_config = config('mail');

    // 新しいconfigを設定
    $replace_swift_mailer(array_merge($current_config, $override_config));

    // 送信
    $result = \Mail::send($view, $data, $callback);

    // 永続化しない場合は設定を元に戻す
    if (!$perpetuate) {
        $replace_swift_mailer($current_config);
    }

    return $result;
}

この関数の第4引数に下記のような配列を渡してやれば、その設定で送信ができるようになります。

<?php
// gmailで送る例
$custom_config = [
    'driver'     => 'smtp',
    'host'       => 'smtp.gmail.com',
    'port'       => 587,
    'username'   => 'hogehoge@gmail.com',
    'password'   => 'xxxxxxxx',
    'encryption' => 'tls'
];

Laravel5.3はMailableインタフェースが追加になっているわけですが、今回の要件にどうすれば対応できるのかも気になる所です。