メールヘッダを自前でエンコードするときに

PHPからメールを送信するのはよくあることだけど、いろんな方法がある。mail関数、mb_send_mail関数、PEAR_Mail、fsockopen・・・。普段はmb_send_mail関数で十分だけど、いろいろな状況下でメールを送信することがあるわけで、複数のやり方を知っておくと非常に役に立つ。日本語でメールを送信するときは、いろいろ問題はあるにせよmb_send_mail関数で十分。実際に送信する前に以下の指定さえしておけば問題ない。

mb_language(’ja’);
mb_internal_encoding(’utf-8′);

正しい言語指定と正しい文字コード指定をしておけばよい。ただし、この関数を知っているだけでは、例えば添付ファイル付きのメールを送信することはできない。実現するためには、関数の使い方以前にメールのお作法を知っておく必要がある。今回はいろいろあって、mail関数を使い、自前でヘッダや本文をエンコードして送信するプログラムを作ったので、それについてメモしておくことにする。

  • mb_encode_mimeheader関数
    メールのヘッダを自前で準備する際(特にマルチバイト文字を含む場合)、お世話になるのがこの関数だ。今回自前でヘッダを用意するので、いくつか覚えたことをメモしておくことにする。
    ヘッダに日本語で記述したい内容といえば送信者名(from)とタイトル(subject)だ。from句は以下のように記述するのが一般的。
    From: 送信者名<メールアドレス>
    ここで送信者名は上記関数を使ってエンコードする必要があるのだが、一工夫しておく必要がある。mb_encode_mimeheader関数の第一引数はエンコードしたい文字列だが、第二引数には文字コードを指定する(オプション)。マニュアルにはmb_internal_encodingと同じエンコードにしなければいけないと書いてある。これはちょっと気にくわない。だったら最初からこんな引数いらないじゃない。
    ここで問題がでる。基本的に文字コードUTF-8で記述しているのだからmb_internal_encoding(’utf-8′)で処理しているはずだ。でもマルチバイトのメール送信ってISO-2022-JPじゃなかったっけ。っていろいろ考えていたら訳がわからなくなってくる。だからこう記述した。

    $fromName=mb_convert_encoding($fromName,’ISO-2022-JP’,'UTF-8′);
    mb_internal_encoding(’ISO-2022-JP’);
    $fromName=mb_encode_mimeheader($fromName,’ISO-2022-JP’);

    文字列は変数に格納されているわけで、それらをいったんISO-2022-JPに変換しておく(メール関連の情報は全て変換すればよい)。その後mb_internal_encodingでISO-2022-JPを指定し、それにしたがってmb_encode_mimeheaderするという手順だ。これでいちおうお作法通りのヘッダを作成することが出来ている(と思う)。

しかし、わからないことがある。mb_encode_mimeheaderの第2引数の指定だ。そもそもmb_internal_encodingと同じ指定をすることが前提なら、そんな引数は必要ないだろうと思う。内部エンコードの指定はあってもいいかもしれないけれど、変換後の文字コード指定があったほうが親切だ。絶対その方がいいに決まっている。

でもなぜ今のような状況なのか。きっとなにか理由があるはずだ。そのうち理解出来るだろうか・・・。あえて探ろうとはしないけどね。

ファイルをアーカイブしてダウンロードさせる

2010/02/13 | その他PEAR全般

複数のファイルを一括してダウンロードしたいというリクエストを頂いた。当然httpでは、例えばFTPのようにサーバのディレクトリをそのまままるごとダウンロードさせるようなことはできない。これを実現するための方法は、ダウンロードしたいファイル群を圧縮して一つのファイルとし、それをダウンロードさせる方法だ。調べてみると、大きく分けて二つの方法(ライブラリ)がありそうだ。一つはPear::File_Archiveを使用する方法、もう一つはphpMyAdminの中に含まれるスクリプトを流用する方法だ。後者はデータベースのダンプを圧縮ダウンロードするためのライブラリだ。後者のライブラリに関する記述を見る限りはかなりスマートに記述出来そうなイメージを持ったが、ライセンスがわからないので使用を控えることにした(クライアントに納品するものだしね)。

前者はもちろんライセンス的には難の問題もない。ただ、個人的に見て記述方法がスマートではないように見える。それがちょっと嫌(でも仕方ない)。スクリプトは以下のように記述した。

require_once “File/Archive.php”;
$obj = File_Archive::toArchive(date(YmdHis).’.zip’, File_Archive::toOutput());
foreach($borings as $boring){
$path=(ファイルのパス);
if($body=@file_get_contents($path)){
$obj->newFile(ファイル名);
$obj->writeData($body);
}
}
$obj->close();

とりあえず、これでダウンロードは出来ることを確認した。ただし一つ問題。lhacaなどのアーカイバではなんの問題もなく解凍できるが、エクスプローラで内容を確認しようとすると「無効です」と怒られてしまった。きっとファイルとして体をなすための許容の範疇ではあるけれども、ちょっとだけファイルが壊れているのだと想像した。なんとなく原因もわかっているけれど。

回避する方法はまた別の機会にでも記述することにする。

ini_set(’include_path’,ini_get(’include_path’).’:’.dirname(__FILE__).’/../../pear/PEAR’);
require_once “File/Archive.php”;
$obj = File_Archive::toArchive(date(YmdHis).’.zip’, File_Archive::toOutput());
foreach($borings as $boring){
$path =dirname(__FILE__).’/../../data/’.substr($boring,0,2).’/DATA/BED’.$boring.’.XML’;
if($body=@file_get_contents($path)){
$obj->newFile(’BED’.$boring.’.XML’);
$obj->writeData($body);
}
}
$obj->close()

ブラウザの戻るボタンでフォームが消える

2010/01/27 | その他

お問い合わせフォームなどで、送信ボタンの直前に「プライバシーポリシー」へのリンクを用意しておくのはよくあること。このリンクを同じウインドウで開くか別ウインドウで開くか、ということで問題が出ることがある。

別ウインドウで開く場合、例えばポップアップ用として別途(ポップアップ専用のヘッダとフッタをつけた)HTMLをコーディングしておくのはひとつの手段。しかし大抵の場合はプライバシーポリシーは、通常デザインのものがあるはずなので、おのずと2ページのプライバシーポリシーができてしまう。これはプライバシーポリシーを改訂する際は面倒だ。

かといって、通常のヘッダとフッタのままのHTMLをポップアップさせるのはイマイチ。

同じウインドウで開く場合だが、送信ボタンの直前にリンクを配置すると、たいていフォームに入力した後にそのリンクをたどることになる。その場合、リンク先を見た後、ブラウザの戻るボタンでフォームに戻るのだが・・・入力した内容が消えている、ということもあるはずだ。

これを回避する方法。session.cache_limiterの値をnoneにしておく。ブラウザのキャッシュを制限しないためのヘッダを出力してくれる。これでフォームページから離脱してブラウザの戻るボタンで戻ってもフォームの値が残っている。

Spreadsheet_Excel_Reader

2010/01/26 | PHPライブラリ

仕事でSpreadsheet_Excel_Readerを使ったんだけど、どうも腑に落ちない症状が発生する。エクセルファイルを読み込む際にメモリが足りないとエラーが出る。その値がいつも「67108864」になるのだ。つまり64MB。いつでも64MBなのだ。

memory_limitを4MBにしても128MBにしても同じ数字が出る。なぜだ。

ライブラリ内にもmemory_limitの記述がないのだが、どこかに何らかの記述があってそれが影響しているのだろうけど、でもそれがなんだかわからない。調べる時間もない。

とりあえずすぐに手を付けることは出来ないけれど、そのうち時間が取れたらPHPExcelで試してみることにする。いずれにしても情報は少なそうだけど。

Cache_Liteのバグか

2010/01/25 | Cache_Lite

PEARのCache_Liteはいつもよく使っているのだが、これってバグじゃないか、と思える症状に出くわした。

if(!$lines=$cacheObj->get($cacheId)){
$lines=hoge();
$cacheObj->save($lines,$cacheId);
$this->cached[$cacheId]=$lines;
}

上記の処理は、キャッシュIDを指定してキャッシュされているかどうかチェックし、キャッシュがなければhoge()関数を使ってデータを取り出して、改めてキャッシュに保存する、という処理を書いている。

この処理自体はなんの問題もなく動作する。しかしこの処理を、ひとつのスクリプト内で2回呼び出すとエラーになる。「そもそも1回取得しているデータなんだから2回も呼び出すことはないだろ」と言われるかもしれないが、例えばスクリプトをクラス化し、共通のデータをそれぞれ別の処理で使用するようなとき、2回呼び出したほうが記述がスマートな場合もある。

回避方法としては、結局データを一時的に格納する変数を作成し、そこもチェックするようスクリプトに手を加えることで対応した。これってバグだと思う。というか、もしかしたら、Cache_Liteそのものに回避方法があるのだろうか。

といっても最新版ではなくて、1.51の話(2008年6月のリリース)。現時点での最新版は2009年7月の1.7.8。新しいバージョンでチェックしてみなきゃ。っていうか、新しいバージョンを使わなきゃ・・・。