Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
37
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@suin(Craftsman Software)
Organization

SplFileObjectのREAD_AHEAD, SKIP_EMPTY, DROP_NEW_LINEはPHPのバージョンによって挙動がバラバラな件

SplFileObjectでCSVを読み込むとき、よく次のオプションを設定するコードを見かける。

$file = new SplFileObject($path);
$file->setFlags(
    SplFileObject::READ_CSV | 
    SplFileObject::READ_AHEAD | 
    SplFileObject::SKIP_EMPTY | 
    SplFileObject::DROP_NEW_LINE
);

これらのオプションの説明はSplFileObjectの定数のものをそのまま引用する。

  • SplFileObject::DROP_NEW_LINE 行末の改行を読み飛ばします。
  • SplFileObject::READ_AHEAD 先読み/巻き戻しで読み出します。
  • SplFileObject::SKIP_EMPTY ファイルの空行を読み飛ばします。期待通りに動作させるには、READ_AHEAD フラグを有効にしないといけません。
  • SplFileObject::READ_CSV CSV 列として行を読み込みます。

この説明を読む限り、SKIP_EMPTYDROP_NEW_LINEを使って、空行をスキップするような実装ができそうだし、空行をスキップするためにこれらのオプションを使った実装をよく目にする。

しかし、PHPのバージョンよってこれらオプションの動作が全く異なり、2パターンの振る舞いが確認できた。しかも、特定のバージョンから振る舞いが切り替わったわけではなく、特定のバージョンまでを気にすればいいものではないので厄介だ。

論より証拠に、PHP5.4.0から7.3.1のバージョンで、DROP_NEW_LINEREAD_AHEADSKIP_EMPTYの3オプションの全組み合わせ8とおりを試した結果を提示したい。SplFileObjectには2行目と4行目に空行がある次のデータを持ったファイルを与えた。

data.csv
1<LF>
<LF>
3<LF>

出力結果は次のようになった:

PHP 5.4.0〜5.5.20, 5.6.0〜5.6.4, 7.2.0〜7.3.1

オプション 出力
READ_AHEAD SKIP_EMPTY DROP_NEW_LINE [["1"],["3"]]
READ_AHEAD SKIP_EMPTY [["1"],[null],["3"]]
READ_AHEAD DROP_NEW_LINE [["1"],[null],["3"]]
READ_AHEAD [["1"],[null],["3"]]
SKIP_EMPTY DROP_NEW_LINE [["1"],["3"]]
SKIP_EMPTY [["1"],[null],["3"]]
DROP_NEW_LINE [["1"],[null],["3"]]
[["1"],[null],["3"]]

PHP 5.5.21〜5.5.38, 5.6.5〜5.6.38, 7.0.0〜7.1.25

オプション 出力
READ_AHEAD SKIP_EMPTY DROP_NEW_LINE [["1"],["3"]]
READ_AHEAD SKIP_EMPTY [["1"],[null],["3"]]
READ_AHEAD DROP_NEW_LINE [["1"],[null],["3"],[null]]
READ_AHEAD [["1"],[null],["3"],[null]]
SKIP_EMPTY DROP_NEW_LINE [["1"],["3"],false]
SKIP_EMPTY [["1"],[null],["3"],false]
DROP_NEW_LINE [["1"],[null],["3"],[null]]
[["1"],[null],["3"],[null]]

検証に使ったコード

<?php

$flags = [
    'READ_AHEAD' => SplFileObject::READ_AHEAD,
    'READ_AHEAD | DROP_NEW_LINE' => SplFileObject::READ_AHEAD | SplFileObject::DROP_NEW_LINE,
    'READ_AHEAD | SKIP_EMPTY' => SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY,
    'READ_AHEAD | SKIP_EMPTY | DROP_NEW_LINE' => SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE,
];


$file = new SplTempFileObject();
$file->fwrite("1\n\n3\n");
foreach ([SplFileObject::READ_AHEAD, 0] as $readAhead) {
    foreach ([SplFileObject::SKIP_EMPTY, 0] as $skipEmpty) {
        foreach ([SplFileObject::DROP_NEW_LINE, 0] as $dropNewLine) {
            $file->rewind();
            $file->setFlags(
                SplFileObject::READ_CSV | $readAhead | $skipEmpty | $dropNewLine
            );
            printf(
                " %s | %s | %s | %s\n",    // SKIP_EMPTY
                $readAhead ? 'READ_AHEAD' : '          ',
                $skipEmpty ? 'SKIP_EMPTY' : '          ',
                                               // DROP_NEW_LINE
                $dropNewLine ? 'DROP_NEW_LINE' : '             ',
                json_encode(array_values(iterator_to_array($file)))
            );
        }
    }
}

検証環境: Online PHP editor | output for 25GRZ

結論

バージョンによってSplFileObjectのオプションの振る舞いが異なるので、READ_AHEAD, SKIP_EMPTY, DROP_NEW_LINEは避けて空行処理は自前でやるか、DROP_NEW_LINEを使うとセル内の改行が無くなる問題があるため、DROP_NEW_LINEは避けた上でどのバージョンでも振る舞いが同じREAD_AHEADSKIP_EMPTYの組み合わせにしたほうがいい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
37
Help us understand the problem. What are the problem?