symfony1.4の「SwiftMailer」を日本語対応(iso-2022-jp)してみた

symfony1.2までは「jpMailPlugin」を利用していましたが、1.4へとバージョンアップの機会に
「SwiftMailer」を使用することにしました。
基本的に携帯への送信が必須となるので、日本語(JISコード)対応しなければいけません。

とりあえずsymfonyのマニュアル通りに送信してみることに・・・
mb_language('ja');
mb_internal_encoding('UTF-8');

$message = Swift_Message::newInstance()
  ->setFrom('from@hoge.com')
  ->setTo('to@hoge.com')
  ->setSubject(mb_encode_mimeheader('日本語のサブジェクト',
                                    'iso-2022-jp', 'B', "\r\n"))
  ->setBody(mb_convert_encoding('日本語のBody',
                                'iso-2022-jp',
                                mb_internal_encoding()));

$this->getMailer()->send($message);

結果・・・Subject,Bodyともに文字化け。(これは、予想通り)

文字化けしたメールのヘッダ
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
↓ こうしたい
Content-Type: text/plain; charset=iso-2022-jp
Content-Transfer-Encoding: 7bit

文字コードを指定してテスト

 $message = Swift_Message~
 // 文字コード指定
 $message->setCharset('iso-2022-jp');
 // メール本文のエンコード指定
 $message->setEncoder('7bit'); → ここでPHPエラー。

なにやら、getName() メソッドが無いよ!と怒られてしまいます。
setEncoder()の引数を確認してみると、「Swift_Mime_ContentEncoder」というオブジェクト になっています。
SwiftMailerのドキュメントを見ても何もなし・・・。

しょうがないので、ソースを追っていくと、Swift_Encodingというクラスに定義されている模様。

エンコード指定部を、
$message->setEncoder(Swift_Encoding::get7BitEncoding());
にしてみると成功!

これでヘッダは期待通りの出力になり、body部の文字化けもなくなりましたが、 Subjectだけが文字化け・・・
どうも、Subjectが長いと文字化けするみたい。

ググって見ると、どうも前のバージョンのSwift Mailerも長い日本語は文字化けするという情報が。
う~ん、厄介だな~。
結局、Subjectで使用するヘッダのクラスごと変更することに。

/**
 * 日本語対応(iso-2022-jp)メールヘッダクラス
 */
class kzl_Jp_Swift_Mime_Headers_UnstructuredHeader
  extends Swift_Mime_Headers_UnstructuredHeader
{
  // override
  public function getFieldBody()
  {
    if (!$this->getCachedValue())
    {
      // ISO-2022-JP対応
      if (strcasecmp($this->getCharset(), 'iso-2022-jp') === 0)
      {
        // subjectをセットする際にエンコードするのでここでは何もしない
        $this->setCachedValue($this->getValue());
        // 本来はこちらが正しいと思う
        // $this->setCachedValue(mb_encode_mimeheader($this->getValue(),
        //                                              $this->getCharset(), 'B', "\r\n"));
      } else {
        parent::getFieldBody();
      }
    }
    return $this->getCachedValue();
  }
}

上記クラスを作成し、libフォルダ以下へ保存。
使用方法は、

// Subjectヘッダを削除
$message->getHeaders()->remove('Subject');
// 新ヘッダクラスのインスタンス作成。
$subjectHeader
  = new kzl_Jp_Swift_Mime_Headers_UnstructuredHeader('Subject',
             new Swift_Mime_HeaderEncoder_Base64HeaderEncoder());
// 文字コードセット
$header->setCharset('iso-2022-jp');
// ヘッダを設定
$message->getHeaders()->set($header);

これで、長いSubjectの文字化けもなくなりました。
メール処理を実装する度にこの処理を記述するのは手間がかかるので、最終的に
Swift_Messageを継承して以下のクラスを 作成しました。
From・To等のアドレス+名称のエンコードも、このクラス内で処理しています。
(本来は、Subjectのヘッダのように、独自のクラス内でエンコードするのが正しいと思うけど・・・)

<?php
/**
 * 日本語送信用Swift_Messageクラス
 */
class kzl_Jp_Swift_Message extends Swift_Message
{
  public function __construct($subject = null, $body = null)
  {
    mb_language('ja');
    mb_internal_encoding('UTF-8');

    $contentType = 'text/plain';
    $charset = 'iso-2022-jp';

    call_user_func_array(
      array($this, 'Swift_Mime_SimpleMessage::__construct'),
      Swift_DependencyContainer::getInstance()
        ->createDependenciesFor('mime.message')
      );

    // Subjectヘッダのクラスを自前のクラスに変更
    $this->getHeaders()->remove('Subject');
    $header = new kzl_Jp_Swift_Mime_Headers_UnstructuredHeader(
                 'Subject',
                  new Swift_Mime_HeaderEncoder_Base64HeaderEncoder()
    );
    $header->setCharset($charset);
    $this->getHeaders()->set($header);

    // ヘッダのソート(一応あわせておく)
    $this->sortHeader();

    // charset, Content-Typeの設定
    $this->setCharset($charset);
    $this->setContentType($contentType);

    // Content-Transfer-Encodingの設定
    $this->setEncoder(Swift_Encoding::get7BitEncoding());

    $this->setSubject($subject);
    $this->setBody($body);
  }

  // override
  public static function newInstance($subject = null, $body = null,
    $contentType = null, $charset = null)
  {
    // content-type ,charsetは固定なので無視する
    return new self($subject, $body);
  }

  // ---------- Swift_Mime_SimpleMessage のメソッドをoverride ----------
  public function setSubject($subject)
  {
    return parent::setSubject($this->_mb_encode_mimeheader($subject));
  }

  public function setFrom($addresses, $name = null)
  {
    if(isset($name))
    {
      $name = $this->_mb_encode_mimeheader($name);
    }
    return parent::setFrom($addresses, $name);
  }

  public function setTo($addresses, $name = null)
  {
    if(isset($name))
    {
      $name = $this->_mb_encode_mimeheader($name);
    }
    return parent::setTo($addresses, $name);
  }

  public function setBody($body, $contentType = null, $charset = null)
  {
    // content-type ,charsetは固定なので無視する
    return parent::setBody(mb_convert_encoding(
      $body,
      $this->getCharset(),
      mb_internal_encoding()));
  }
  //      ~省略。 CC,BCC等も同様にoverride

  // ----------------------------------------------------

  /**
   * Swift_Mime_SimpleMessageのコンストラクタ内からコピー。
   * ヘッダの順番を設定している模様。
   */
  private function sortHeader()
  {
    $this->getHeaders()->defineOrdering(array(
      'Return-Path',
      'Sender',
      'Message-ID',
      'Date',
      'Subject',
      'From',
      'Reply-To',
      'To',
      'Cc',
      'Bcc',
      'MIME-Version',
      'Content-Type',
      'Content-Transfer-Encoding'
      ));
  }

  /**
   * ヘッダの日本語文字列エンコード処理
   * @param string $value
   * @return string
   */
  private function _mb_encode_mimeheader($value)
  {
    return mb_encode_mimeheader($value, $this->getCharset(), 'B', "\r\n");
  }
}
?>

最終的な、メール送信処理は以下のようになります。

$message = kzl_Jp_Swift_Message::newInstance()
 ->setFrom('from@hoge.com', '日本語の名前From')
  ->setTo('to@hoge.com', '日本語の名前To')
  ->setSubject('日本語のながいながい件名です。')
  ->setBody("日本語の本文。\r\n改行もOK");

$this->getMailer()->send($message);

エンコード処理等の記述もなくなり、大分すっきりしました。

それにしても、symfony1.4の情報少ないな~。
皆さん、まだ様子見なのかな。

ソースがほしい方はこちらから-kzlJpSwiftMailerPlugin-
(CC,BCC等のエンコードも実装済み。)
クラスファイルは2本しかないので、libフォルダへ突っ込むなり、Pluginとして使用するなりご自由に。
※ ご利用による事故・不具合による責任は追いかねますので、十分に検証の上自己責任でご利用ください。


—2010/3/5 追記

 ダウンロードファイル内に、日本語対応のメール用Webデバッグパネルを含めました。
 Pluginとして利用する場合は、自動で有効になります。
 

—2010/9/22 追記—
ご指摘により、kzl_Jp_Swift_Message内、addBcc, setSenderの変数名のタイプミスを修正しました。
「あさり」様ありがとう御座いました

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL