ファイルを読み込んだり書き込んだりする時には、file_get_contents()
や file()
や file_put_contents()
が非常に便利です。
ただ、引数に色んなものを渡せるせいで、「サクッと書きたい時にまだるっこい(まどろっこしい)」という事がままありますので…
- 引数にあれこれ渡さなくても、よく使うであろう処理をサクっと書ける
- URL の処理等は考慮しない
- ファイルのロック処理にもある程度気をつけてみる
あたりを意識して、コードを書いてみます。PHP 5.2.6 ~ PHP 7.3.X まで、幅広い環境で動作すると思います。
ファイルを読み込む
function file_get($path, $offset = -1, $maxLength = -1)
{
if (!$fp = fopen($path, 'rb')) { return false; }
flock($fp, LOCK_SH);
$data = stream_get_contents($fp, $maxLength, $offset);
flock($fp, LOCK_UN);
fclose($fp);
return $data;
}
stream_get_contents() を使えば、while
でループさせる必要すらないので楽ですが、offset
length
の引数の順番が、file_get_contents() とは逆なところが、いかにも PHP らしい罠です(汗)1
substr()
等でも、引数は offset
length
の順に渡しますので、その仕様に合わせています。
使い方
//-- 単純にファイルの中身が欲しい場合
$data = file_get('/path/to/file.txt');
//-- 5byte目以降を全て読み込む
$data = file_get('/path/to/file.txt', 5);
//-- 最初の5byteだけ読み込む
$data = file_get('/path/to/file.txt', 0, 5);
ファイルへ上書きする
file_put_contents($path, $data, LOCK_EX)
に該当する処理です。既に $path
の中身がある場合でも、$data
で上書きされます。
function file_put($path, $data)
{
if (!$fp = fopen($path, 'cb')) { return false; }
flock($fp, LOCK_EX);
ftruncate($fp, 0);
$result = fwrite($fp, $data);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
return $result;
}
使い方
file_put('/path/to/file.txt', $data);
ファイルの末尾へ追記する
file_put_contents($path, $data, FILE_APPEND | LOCK_EX)
に該当する処理です。既に $path
の中身がある場合は、$data
がファイルの末尾に追記されます。
function file_append($path, $data)
{
if (!$fp = fopen($path, 'ab')) { return false; }
flock($fp, LOCK_EX);
$result = fwrite($fp, $data);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
return $result;
}
使い方
file_append('/path/to/file.txt', $data);
ファイルの先頭へ追加する
file_put_contents()
だけではできない処理です。既に $path
の中身がある場合は、$data
がファイルの先頭に追加されます。
function file_prepend($path, $data)
{
if (!$fp = fopen($path, 'c+b')) { return false; }
flock($fp, LOCK_EX);
$data = $data . stream_get_contents($fp);
rewind($fp);
$result = fwrite($fp, $data);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
return $result;
}
使い方
file_prepend('/path/to/file.txt', $data);
注意点
軽くググってみる と見つかるコードは、ファイルロックを全く考慮していないものばかりのようですので、ご注意ください。
ファイルを配列に読み込む
UTF-8 以外の文字コードにも対応させたものです。
file()
関数では、FILE_IGNORE_NEW_LINES
(各行の最後の改行を省略する)を指定する事が圧倒的に多いと思いますので、そこは指定しなくて済むようにしています。
また、FILE_SKIP_EMPTY_LINES
(空行を読み飛ばす)を指定する事も多いと思いますので、空行を読み飛ばす動作がデフォルトになっています。
function file_get_array($path, $isSkipEmptyLines = true)
{
if (($data = file_get($path)) === false) { return false; }
$data = mb_convert_encoding($data, 'UTF-8', mb_detect_encoding($data, mb_detect_order(), true));
return ($isSkipEmptyLines) ? preg_split('/\r\n|\n|\r/', $data, -1, PREG_SPLIT_NO_EMPTY)
: preg_split('/\r\n|\n|\r/', $data);
}
使い方
$array = file_get_array('/path/to/file.txt');
//-- 空行を読み飛ばさない時
$array = file_get_array('/path/to/file.txt', false);
昔話
perl や PHP4 の頃には、file_get_contents()
や file_put_contents()
なんて関数はありませんでしたので、PHP 5 で初めてこれらを使った時は、その便利さに感動したものです。
ところが…
-
file_get_contents()
ってファイルロック処理がないよね- きちんとロックしようとすると、ファイルオープン 1 回の中で読み込み~書き込みする必要があるので、この関数の用途的に、不要といえば不要なのかも。
-
file_put_contents()
のロック処理ってきちんと働いてなくない!?- ※PHP5.2.6 で修正されました。
c
モードも、このバージョンで実装されました。
- ※PHP5.2.6 で修正されました。
-
ファイルロック解除の際は、
flock($fp, LOCK_UN)
を書いたら駄目らしいよ!?- ※PHP5.3.2 で修正されました。アンロック処理を明示するのが正しいです。
-
file_get_contents()
の第 2 引数って邪魔じゃない?- 特に、ファイルの一部を読みたい時に、第 2 第 3 引数に
false, null
と渡すのが…。
- 特に、ファイルの一部を読みたい時に、第 2 第 3 引数に
-
file_get_contents()
やfile()
には URL も渡せるけど、色々とイケてないよね…。- タイムアウト処理とか
$http_response_header
とか…。
- タイムアウト処理とか
…という、何ともむず痒い…「痒いところまで手は届くのだけど、背中を掻きたい時に、足の裏を掻いてしまう感じ」…なので、結局こういったものを用意して使ってました。2
今から 10 年ぐらい前?の話です(遠い目)
大昔のコードを元に書いてますので、おかしなところがありましたら、ご指摘ください。
-
歴史的に、offset パラメータが後から追加されたため、このような順番になってしまったようです。 ↩
-
file_get_contents()
で、HTTP の POST 処理なんかまで書ける実用主義?なところは、とても PHP らしいと思います。ただ、Beauty Is in Simplicity とも言いますので…。 ↩