CakePHPで日本語メールを想定したテストケースを書くとテストに失敗する
CakePHP 1.3.7で日本語メールを送る時の問題がいろいろありましたので、日本語('ISO-2022-JP')のメールを想定したテストケースを書いてみました。テスト内容は件名がきちんとエンコードされているかのテストになっています。
#CakePHPの日本語メールにはこのほかにも改行などの問題もありますが、少しでも改善できればと思います。
テストケースの修正
コメントアウトしているのがオリジナルのemail.test.phpのコードです。
mb_internal_encodingを操作したりmb_internal_encodingが元に戻ったかどうかのテストは必要がないのでコメントアウトしています。(後述)
function test_encodeSettingInternalCharset() { $skip = !function_exists('mb_internal_encoding'); if ($this->skipIf($skip, 'Missing mb_* functions, cannot run test.')) { return; } //mb_internal_encoding('ISO-8859-1'); //$this->Controller->charset = 'UTF-8';//'EmailTest'が抜けてる? $this->Controller->EmailTest->charset = 'ISO-2022-JP'; $this->Controller->EmailTest->to = 'postmaster@localhost'; $this->Controller->EmailTest->from = 'noreply@example.com'; //$this->Controller->EmailTest->subject = 'هذه رسالة بعنوان طويل مرسل للمستلم'; $this->Controller->EmailTest->subject = 'CAKEは甘くて美味しい'; $this->Controller->EmailTest->replyTo = 'noreply@example.com'; $this->Controller->EmailTest->template = null; $this->Controller->EmailTest->delivery = 'debug'; $this->Controller->EmailTest->sendAs = 'text'; $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); //$subject = '=?UTF-8?B?2YfYsNmHINix2LPYp9mE2Kkg2KjYudmG2YjYp9mGINi32YjZitmEINmF2LE=?=' . "\r\n" . ' =?UTF-8?B?2LPZhCDZhNmE2YXYs9iq2YTZhQ==?='; $subject = '=?ISO-2022-JP?B?GyRCI0MjQSNLI0UkTzRFJC8kRkh+TCMkNyQkGyhC?='; preg_match('/Subject: (.*)Header:/s', $this->Controller->Session->read('Message.email.message'), $matches); $this->assertEqual(trim($matches[1]), $subject); //$result = mb_internal_encoding(); //$this->assertEqual($result, 'ISO-8859-1'); }
上記のように修正ししたemail.test.phpのソース(UTF-8)を保存してテストを実行するとテストに失敗します。実際にメールを送信すると文字化けする事になります。
テストに失敗する理由
なぜ失敗するかというと、件名の元の文字コードが'UTF-8'であるにも関わらず「 'ISO-2022-JP'から 'ISO-2022-JP'へ変換」してしまっているからです。
ソースで原因を探ると、Emailコンポーネントが件名などをエンコードを実行する時に_encodeメソッドでmb_internal_encodingがどのような設定であっても文字コードを「Email->charsetの設定値からEmail->charsetの設定値へ変換」しているからです。
本来は「 渡された文字列の文字コードからEmail->charsetの設定値へ変換」する必要があります。今回のケースでは 'UTF-8'から 'ISO-2022-JP'へ変換」する必要があります。
修正前のコードでテストが成功する理由
オリジナルのテストケースでは件名の元の文字コードとEmail->charsetが両方とも'UTF-8'だったので「 'UTF-8'からUTF-8'へ変換」が問題なく行われたからです。
Emailコンポーネントの修正とinternal_encoding設定
Emailコンポーネントの修正
Emailコンポーネントの修正点はmb_internal_encodingの操作を削除するだけです。
email.php
function _encode($subject) { $subject = $this->_strip($subject); $nl = "\r\n"; if ($this->delivery == 'mail') { $nl = ''; } /* ここ不要 $internalEncoding = function_exists('mb_internal_encoding'); if ($internalEncoding) { $restore = mb_internal_encoding(); mb_internal_encoding($this->charset); } */ $return = mb_encode_mimeheader($subject, $this->charset, 'B', $nl); /* ここも不要 if ($internalEncoding) { mb_internal_encoding($restore); */ return $return; }
この修正で「internal_encodingからEmail->charsetへ変換」されるようになります。あとはinternal_encoding設定です。
適切なinternal_encoding設定
マルチバイトのエンコードを行うためのmb_encode_mimeheader関数は変換元文字コードを直接指定できず、internal_encodingを利用しています。internal_encodingを正しく設定していないとmb_encode_mimeheaderで正しくエンコードできません。
多くの場合、ソースコードとinternal_encodingが一致するように設定しておくと何かと都合が良いはずでので、アプリケーション全体で有効になるようにphp.iniで設定しておきます。
php.ini
mbstring.internal_encoding = UTF-8
もしphp.iniで設できなければスクリプト内でmb_internal_encoding('UTF-8')なとど設定します。
また、変換元の文字コードが場合のより異なる場合には都度mb_internal_encodingを使用して下さい。
EUC-JPの場合
今回のテストケースでは'UTF-8'でしたが、ソースコードとinternal_encodingがEUC-JPの場合でもテストが成功します。
蛇足
- CakePHP 1.3.7では件名のほか、各アドレス欄もMIMEエンコードされる
- 1.3.4〜1.3.6では各アドレス欄のエンコードはしていなかった
- 本文のヘッダは'Content-Transfer-Encoding: 7bit'で固定
- 本文の文字コード変換、エンコードは行われない
- ワードラップはマルチバイトを考慮していない(wordwrap関数使用)
検索
最近の投稿
作ったもの
写真共有のTWINGAR
CakePHPのまとめノートCakePHP Note
CakePHPのAPIFramework API
About Me
@ZiSTA Tweets
CakePHPとかMacとか

コメント