LoginSignup
24
21

More than 3 years have passed since last update.

PHPのストリームフィルタでCSV読み込み

Last updated at Posted at 2014-08-08

※この実装はバグがあります。コメントおよび正しい実装を参照してください。

利用できるフィルタのリスト』という謎の項目があります。
filter_input()とかのFilterとはまた別の、PHPにデフォルトで用意されているフィルタです。

何ができるかというと、入出力ストリームに対して操作が可能です。
ストリームとはfopen()とかでオープンされるリソースで、というか他に何か使えるものあるんですかね?

<?php
    $text = 'abcdefg';

    // ストリームフィルタ
    $crypt   = 'mcrypt.'.MCRYPT_3DES;
    $decrypt = 'mdecrypt.'.MCRYPT_3DES;
    $params = array('iv'=>'hoge', 'key'=>'fuga');

    // 書き込み
    $fp = fopen('foo.txt', 'w');
    // 暗号化ストリームフィルタを適用
    stream_filter_append($fp, $crypt, STREAM_FILTER_WRITE, $params);
    fwrite($fp, $text);
    fclose($fp);

    // 読み出し
    $fp = fopen('foo.txt', 'rb');
    // 復号ストリームフィルタを適用
    stream_filter_append($fp, $decrypt, STREAM_FILTER_READ, $params);
    $text = fread($fp, 10000);
    fclose($fp);

    var_dump($text); // abcdefg

foo.txtは人間には読めない形に暗号化されていますが、fread()するだけで透過的に復号されます。
あとはラッパークラスなりで囲えば簡単に暗号化ファイル操作クラスができあがります。

デフォルトのフィルタには、str_rot13()みたいな絶対使わないのがあるわりに有用なものがあまりありません。
ストリームフィルタは自作可能なので、役に立ちそうなものを自作してみます。

<?php
    /*
    * SJISのCSVをUTF-8で取り込むフィルタ
    */
    class fgetcsv_filter extends php_user_filter{
        /**
        * @Override
        * @param resource 入力ストリーム
        * @param resource 出力ストリーム
        * @param int 変更したデータ長を参照渡しで返す
        * @param boolean フィルタチェインの最後の処理であればtrue
        * @return int PSFS_PASS_ON / PSFS_FEED_ME / PSFS_ERR_FATAL
        */
        public function filter($in, $out, &$consumed, $closing){
            $locale = setlocale(LC_ALL, 0);
            setlocale(LC_ALL,'ja_JP.UTF-8');
            while ($bucket = stream_bucket_make_writeable($in)) {
                $bucket->data = mb_convert_encoding($bucket->data, 'UTF-8', 'SJIS-win');
                $consumed += $bucket->datalen;
                stream_bucket_append($out, $bucket);
            }
            setlocale(LC_ALL, $locale);
            return PSFS_PASS_ON;
        }
    }
    // fgetcsv_filterをfgetcsv_regという名前で登録
    stream_filter_register('fgetcsv_reg', 'fgetcsv_filter');

    $fp = fopen('hoge.csv', 'r');
    // $fpにfgetcsv_regフィルタを適用
    stream_filter_append($fp, 'fgetcsv_reg');

    // 以後普通に読める
    $data = fgetcsv($fp);
    var_dump($data);

hoge.csv

1ソ,Ⅱ表,③能,"ⅳ
"

CSVの文字コードはSJISです。
結果。

  array(4) {
    [0]=>
    string(4) "1ソ"
    [1]=>
    string(6) "Ⅱ表"
    [2]=>
    string(6) "③能"
    [3]=>
    string(5) "ⅳ
"
  }

文字化けせずに読み込み成功しました。
めでたし。

まあ正直わかりにくいので、あえてストリームフィルタを使わなくてもfgetcsv_reg()でいいじゃないという気もしますが。

24
21
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
21