Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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の組み合わせにしたほうがいい。

suin
Qiita 4位/TypeScript入門書執筆中/TypeScripterのための座談会「YYTypeScript」主催/『実践ドメイン駆動設計』書籍邦訳レビュア/分報Slack考案/YYPHP主催/CodeIQマガジン執筆/株式会社クラフトマンソフトウェア創設/Web自動テスト「ShouldBee」の開発/TypeScript/DDD/OOP
https://yyts.connpass.com/
shouldbee
開発者向けテスト支援サービスShouldBeeを開発・運営するスタートアップ(onlab第8期)
http://shouldbee.at
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした