SPLのsplFileObjectを拡張してエンコード変換できるようにしてみた

なにやらPHPでモダンがアツイ(何をいまさら)ようなので・・・。


PHPでテキストファイルを読み込む場合、内部はUTF-8だけどファイルはShift-JISって場合がたまにあり、読み込んでからエンコード変換をかけないといけない場合があります。
PHP的なスタンダートなやり方は、1行ずつ読み込みながらmb_convert_encodingで変換していくという感じでしょうか。
この手の処理を、SPLのクラスやPHP 5.3の機能を使ってスマートに(モダンに?)書けないか、ちょっと考えてみました。

ちなみにJavaC#だと、標準でこのあたりの機能が備わっていますよね。


splFileObjectを使ってテキストファイルを読み込む場合、splFileObjectのイテレータを使って、次のようなPHP的モダン?な記述でファイルを読み込むことができます。

<?php
$file = new splFileObject('test.csv');
foreach ($file as $value)
{
    var_dump($value);
}

エンコード変換が必要な場合、foreachの中で毎回mb_convert_encodingすれば済む話なのですが、なんだかそれはモダンじゃないw

で、次のようにsplFileObjectの派生クラスを作りました。

<?php
class   myFilterableFileObject  extends SplFileObject
{
    protected   $filter_func = null;

    public function __construct($filename, $filter_func = null)
    {
        if (is_callable($filter_func))
        {
            $this->filter_func = $filter_func;
        }
        return parent::__construct($filename);
    }

    public function current()
    {
        $value = parent::current();
        if (is_callable($this->filter_func))
        {
            $filter_func = $this->filter_func;
            if($this->getFlags() & self::READ_CSV)
            {
                $temp = array();
                foreach ($value as $element)
                {
                    $temp[] = $filter_func($element);
                }
                $value = $temp;
            }
            else
            {
                $value = $filter_func($value);
            }
        }
        return $value;
    }
}

これを使って、次のようにコンストラクタでフィルタを渡します。

<?php
require_once 'myFilterableFileObject.class.php';

$file = new myFilterableFileObject('test.csv', function($value){
    return mb_convert_encoding($value, 'UTF8', 'SJIS');
});

//$file->setFlags(splFileObject::READ_CSV); //CSV読み込みモード

foreach ($file as $value)
{
    var_dump($value);
}

ここでは、文字列のエンコードを変換する無名関数をフィルタに指定しています。こうすると、foreachの中で特別なことをしなくても、エンコード変換が行われたデータを取り出すことができます。

また、エンコードの変換以外に文字列のトリムやその他の処理も同時に行うことができますね。

※コードの記述量を減らす目的なら、デフォルトのエンコード変換フィルタをクラス内に組み込んだ方がよいかもしれません。



ちなみに、myFilterableFileObjectのcurrentメソッドの中が多少ややこしくなっているのは、splFileObjectには「CSV読み込みモード」という特殊なモードがあって、このフラグを設定しておくと、行の読み取り時に自動的にCSVパースが行われ、その結果が返るようになります。
上のコードでは、foreachの中の$valueが配列になります。


今後SPLが充実していくと、この程度は自前でクラスを拡張しなくても、標準のクラス群だけでできるようになったり・・・しませんかね;;


P.S.
書き忘れましたがPHP 5.3でのコードです。