2013年6月6日木曜日

SwiftMailerでsubjectが文字化け

Symfony2でアプリケーションからメールを送信する際、標準のSwift Mailerを使うのが一般的でしょう。 PHPメンターズさんの記事「Practical Symfony #15: Swift Mailerによる日本語メールの作成」がとても参考になります。

私もこの記事のコードを参考にしてsubject, from, 本文に日本語を使ったメールが問題なく送信できることを確認していたのですが、この送信ロジックを複数のコントローラ内に散乱させてしまっていたので、サービスにして共通化しようとリファクタリングを始めました。

mimeが効かない

services:
    my_mailer:
        class:     MyBundle\Service\Mailer
        arguments:
            - @mailer
            - @templating
            - @translator
            - @logger
とサービスを定義して、Switf_Mailerをコンストラクタ内で注入して、sendメソッドでは$this->mailer->send()で送信。コントローラ内のロジックをサービスに集約する、ごくごく普通のリファクタリングをしたつもりでした。

ところが、このリファクタリングを入れてからsubjectのmimeエンコーディングがされなくなり、いわゆる「文字化け」が発生するようになりました。

原因はわかっているものの

mimeが効いていないので、おそらくは
Swift::init(function () {
    Swift_DependencyContainer::getInstance()
        ->register('mime.qpheaderencoder')
        ->asAliasOf('mime.base64headerencoder');

    Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
});
の設定がうまく反映されていないんだろうという見当はすぐつきました。ただ、なぜサービス内に移しただけで反映しないのかが釈然としません。

シングルトン

さきほどの設定部分のコードを眺めていてわかるのは、
  • Swift::init()はstaticなメソッドである。
  • Swift_DependencyContainerとSwift_Preferencesはシングルトンである。
ここまで来て、ようやく理解できました。私はメールの送信のメソッド内に上記設定を書いていました(PHPメンターズさんの記事ではアプリケーションのバンドルに配置するのを推奨しているにもかかわらず)。Swift::init()はSwift_Mailerのオブジェジェクトが生成される前に実行できていないといけなのですが、サービスで定義してコンストラクタでSwift_Mailerを注入するようになってからSwift::init()を呼んだ時点ではすでにSwift_Mailerのオブジェクトは生成されているため、mimeの設定が反映されない状態になっていました。

解決

mimeの設定をSwiht::init()で行うことを公式ページでも推奨しているのですが、別にsend()毎に実行してもよいのではないか?ということで、
Swift_DependencyContainer::getInstance()
    ->register('mime.qpheaderencoder')
    ->asAliasOf('mime.base64headerencoder');

Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
(snip)
とSwift::init()内から外に出して解決しました。mimeの設定コストがどのくらいなのか計測していませんが、バッチで大量に配信するものでなく、ウェブアプリケーションからなんらかのクライアントのリクエストからキックされてメールを送る用途では、気にしないでいいのではないでしょうか。 このやり方であれば、メール毎にmime設定のオンオフができます(する機会があるかは疑問ですが)

SwiftMailerネタはまだあります

メール本文のテキストの折り返しが意図しないところで行われる挙動にもだいぶ悩まされました。いずれ記事にします。

0 件のコメント:

コメントを投稿