2010年12月27日月曜日

PHPで CSVをダウンロードするための参考サイトとサンプルコード

CSVをダウンロードするWebアプリをPHPで実装する方法。
しばしば必要になるが、そのたびに調べ直してるので、後日のためにまとめてみる。



まとめるとこんな感じか。
$fileName = 'ダウンロードして保存する時のファイル名.csv';
$fileName =  mb_convert_encoding($fileName, 'SJIS-WIN');

header('Content-Type: application/x-csv');
header("Content-Disposition: attachment; filename=$fileName");

$fp = fopen('php://output', 'w');

$rows = /* 配列の集合(DBから取得したデータとか) */

foreach ($rows as $row) {
    mb_convert_variables('SJIS-WIN', mb_internal_encoding(), $row);
    fputcsv($fp, $row);
}

fclose($fp);

2010年12月21日火曜日

PHPの DateTimeオブジェクトは比較演算子で大小を比較できる

大なりや小なりやイコールで大小(前後)を比較できたら便利だなと思い、試してみた。

$time = new DateTime('2011-01-01 01:01:01');
$aftr = new DateTime('2011-01-01 01:01:02');
$same = new DateTime('2011-01-01 01:01:01');

var_dump($time >  $aftr);    // => false
var_dump($time >= $aftr);    // => false
var_dump($time == $aftr);    // => false
var_dump($time <= $aftr);    // => true
var_dump($time <  $aftr);    // => true

var_dump($time >  $same);    // => false
var_dump($time >= $same);    // => true
var_dump($time == $same);    // => true
var_dump($time <= $same);    // => true
var_dump($time <  $same);    // => false

比較できるんだ。知らなかった...

2010年12月10日金曜日

PHPで配列と配列を結合する

PHPの配列はarray_merge()でマージできるが、数値のように+演算子でも結合できる。しかし、微妙に結果が違う。


KEYのある配列の場合

//array_merge
var_dump(array_merge(array('foo' => 1, 'bar' => 2), array('foo' => 3, 'baz' => 4)));

array(3) { ["foo"]=> int(3) ["bar"]=> int(2) ["baz"]=> int(4) }

//+演算子
var_dump(array('foo' => 1, 'bar' => 2) + array('foo' => 3, 'baz' => 4));

array(3) { ["foo"]=> int(1) ["bar"]=> int(2) ["baz"]=> int(4) }

同じKEYがある場合、array_merge()だと後の配列で上書きするのに対し、+演算子だと先の配列が勝つようだ。



KEYのない配列の場合
//array_merge
var_dump(array_merge(array('foo', 'bar'), array('foo', 'baz', 'quux')));

array(5) { [0]=> string(3) "foo" [1]=> string(3) "bar" [2]=> string(3) "foo" [3]=> string(3) "baz" [4]=> string(4) "quux" }

//+演算子
var_dump(array('foo', 'bar') + array('foo', 'baz', 'quux'));

array(3) { [0]=> string(3) "foo" [1]=> string(3) "bar" [2]=> string(4) "quux" }

array_merge()は全ての配列の値を入れた配列を作ってくれるが、+演算子だとインデックスをKEYとみなして同じインデックスがある限り先の配列の値しか残らない。


基本的にarray_merge()を使う習慣にしておいた方が無難そうだ。



参考:ひとつまたは複数の配列をマージする - PHP 5.3 日本語マニュアル

2010年12月7日火曜日

PHPの htmlSpecialChars()の 第3引数(文字コード)の調査メモ

そもそも

基本的に内部的な文字コードは(ソースファイルもDBもその他の設定も)全てUTF-8にしておき、POSTやGETなどで入力値を受け取る時には入力値をUTF-8に変換してから使うのが良いと思うし、そうすれば漏れがない限りはhtmlSpecialChars()等で出力時に不正文字コードを気にする必要はない。



しかし

現実的にはPOST/GETで受け取る以外にも下記の様に様々な方法でデータが入ってくることもある。

それぞれデータを取り出す時に適切に文字コード変換すべきだが、やっぱりいつか誰かが漏らしそう。

なので、やはり出力時にも不正な文字コードは弾いた方がよい。



で、調べてみた

2009年の後半にいくつかのブログでこのことが論じられていた。(そういえば読んだ記憶が...。詳細は下の方の参考リンクを参照。)
中でも「htmlspecialchars - 第45回PHP勉強会@関東 :: handsOut.jp」が簡潔でわかりやすかった。
PHP5.2.5以降を使い、htmlspecialchars()の第3引数にUTF-8を指定するのが一番堅実のようだ。

蛇足だが、もしUTF-8以外でWebページを入出力したい場合は、mb_http_output()とob_start()を使って出力完了後に文字コードをSJIS-WINなどに変換させるようにしておいて、そこにUTF-8で出力するのが良いと思う。(処理量が多いとオーバーヘッドが気になる?)



以外な落とし穴が

しかし、htmlspecialchars()の文字コード検出には思わぬ落とし穴があった。
htmlspecialchars()は不正な文字コードを見つけても、PHPの設定でdisplay_errorsがfalseの場合しかWarningを出さない
  • display_errors = true → Warningを画面にもログにも出さない
  • display_errors = false → Warningをログにも出す(画面には出さない)
これでは、開発環境では文字コードが不正になりうる箇所に気づかないまま、本番環境でWarningを出しまくるかもしれない。

これは意図的なものなので、修正されない可能性が高そう。(参考:display_errorsが謎の副作用を持っている箇所について - muddy brown thang
また、@htmlspecialchars()に@(アットマーク)を付けてもログへのエラー出力を抑制できなかった。

Warningを出さないようにする対策としては、htmlspecialchars()の手前で強制的に文字コードを変換するという手がある。
$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
echo htmlSpecialCahrs($str, ENT_QUOTES, 'UTF-8');
現在のサーバ性能とPHPの速さなら、こんな富豪的な重複処理もあまり問題にならない環境が多い。
mb_convert_encoding()とhtmlSpecialChars()の検出基準(?)が違うというようなことをどこかで読んだ気がするんで、その場合はこれでもWarningが出るのだろう。
でも、どうせmb_convert_encoding()するのなら、そもそもhtmlSpecialChars()での文字コード指定は要らないか。

ただし、本道としては、せっかく出してくれるWarningを手がかりにして、該当箇所で適切な文字コード変換をするようコードを修正するのが筋だろう。
PHPのWarningはset_error_handlerで捕まえられる。



もう1つ

htmlspecialchars/htmlentitiesはBMP外の文字を正しく扱えない - [php] - 徳丸浩の日記」という落とし穴もあるようだが、これはPHPの5.3.2でFixした。(5.2系は?)
また、(線文字Bは置いておいて)一部の漢字を「使えません。仕様です。」で済ませられるなら、こちらの問題は黙殺できそうだ。



参考

ブログ アーカイブ

tags