Menu

PHPのfopen():ファイルを開いて読み書きする(モードの解説)

fopen('data.txt', 'r')はファイルを開いてハンドルを返し、失敗するとfalseを返します。ハンドルからはfgets()やfread()で読み、fwrite()で書き、fclose()で閉じます。モード('r'、'w'、'a'、'x'、'c'とその+の形)で何ができるかが決まります。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

fopen('data.txt', 'r')はファイルを開いてハンドルを返します。ハンドルはストリームリソースで、ほかのファイル関数が1つ目の引数として受け取ります。fgets()(1行)やfread()(指定したバイト数)で読み、fwrite()で書き、fclose()で閉じます。2つ目の引数はモードで、読むなら'r'、書くなら'w'、追記するなら'a'です。

出力:

Got: apple
Got: banana
Got: cherry

ファイルを開けないと(rモードで存在しない、フォルダーがない、権限がない)fopen()はfalseを返し、PHPは次のような警告を表示します。

Warning: fopen(missing.txt): Failed to open stream: No such file or directory in /home/index.php on line 2

そのため、ハンドルを使う前にfalseかどうかを確認します。小さなファイル全体を1回の呼び出しで読み書きするなら、file_get_contents()とfile_put_contents()のほうが短く書けます。fopen()は、少しずつ読むファイルや開いたままにしておくファイルのためのものです。

fopenのモード

モード読み書きファイルがあるときないとき開始位置
rできるできない開く失敗先頭
r+できるできる開く、中身を保つ失敗先頭
wできないできる空にする作る先頭
w+できるできる空にする作る先頭
aできないできる中身を保つ作る末尾、書き込みは常に追記
a+できるできる中身を保つ作る末尾、書き込みは常に追記
xできないできる失敗作る先頭
x+できるできる失敗作る先頭
cできないできる中身を保つ作る先頭
c+できるできる中身を保つ作る先頭

驚かれる行が2つあります。r+は挿入せずに既存のバイトをその場で上書きし、a+はfseek()でポインターを動かしたあとでも常に末尾に書き込みます(ポインターを動かして影響があるのは読み込みだけです)。

出力:

Xbc
Xbc
Xbc
end

Windowsでは'rb'や'wb'を見かけることがあります。bフラグはバイナリの意味で、Windowsが改行コードを変換するのを防ぎます。LinuxとmacOSでは何もしないので、付けても害はなく、移植性も保てます。

fgets()でファイルを1行ずつ読む

fgets()は\nを含めて次の行を返し、ファイルの終わりでfalseを返します。メモリに置くのは一度に1行だけなので、このループはどんな大きさのログファイルでも扱え、breakで早めに止めることもできます。

出力:

2 errors in 5 lines
line 2: database timeout
line 4: disk full

falseとは素のwhile ($line = fgets($fh))ではなく!==で比較しましょう。改行なしで"0"だけを含む行(ファイルの最後の行がそうなることがあります)はfalseと評価され、ループが早く終わってしまいます。

fwrite()で書き込む、追記する

fwrite()は現在の位置に文字列を書き込み、書き込んだバイト数を、失敗すればfalseを返します。fputs()は同じ関数の別名です。ファイルの中身に触れずに末尾に追加するには、'a'で開きます。

出力:

wrote 12 bytes
wrote 13 bytes
wrote 15 bytes
visit /home
visit /about
visit /contact
visit /home

PHP_EOLはPHPが動いているシステムの改行コードです(LinuxとmacOSでは\n、Windowsでは\r\n)。ほかのプログラムが読むファイルでは、明示的に1つを選びましょう。たいていは"\n"です。

fgetcsv()とfputcsv()でCSVファイルを読み書きする

fputcsv()は配列をCSVの1行として書き込み、カンマ、引用符、改行を含むフィールドを引用符で囲みます。fgetcsv()は1行を配列として読み戻します。区切り文字、囲み文字、空のエスケープ文字を明示的に渡しましょう。そうすれば標準的なCSVになり、PHP 8.4ではエスケープの引数を省くと非推奨の通知が表示されます。

出力:

name,city,note
Ada,London,"likes ""math"", a lot"
Linus,Helsinki,kernel

Ada lives in London: likes "math", a lot
Linus lives in Helsinki: kernel

explode(',', $line)で行を分割すると、フィールドにカンマが含まれたとたんに壊れます。fgetcsv()なら引用符の処理をしてくれます。セミコロン区切りのファイル(ヨーロッパのExcelのエクスポートでよくあります)には、区切り文字として';'を渡します。

fseek()、ftell()、rewind()でファイルの中を移動する

開いたファイルには位置があります。読み込みと書き込みはそこから始まり、位置を前へ進めます。ftell()は今の位置を教え、fseek()はバイトのオフセットに位置を動かし、rewind()は先頭に戻ります。w+なら、同じハンドルでファイルを書いて読み戻せます。

出力:

11
world
hello world

flock()でファイルをロックする

カウンターを読み、1を足して書き戻す2つのリクエストが同時に動くと、両方が41を読んで両方が42を書くことがあります。LOCK_EXを指定したflock()は、最初のリクエストがロックを解放するまで2つ目を待たせます。ここではc+が正しいモードです。必要ならファイルを作り、w+と違ってロックを取る前に空にしません。

出力:

Visitor number 1
Visitor number 2
Visitor number 3

ロックは助言的なもので、同じくflock()を呼ぶほかのコードしか止めません。1行を追記するだけなら、file_put_contents($path, $line, FILE_APPEND | LOCK_EX)が1回の呼び出しで同じことをします。

よくあるミス:while (!feof($fh))

古いチュートリアルの多くはfeof()でループします。feof()は読み込みがすでに終わりに達したあとでしかtrueを返さないので、ループは行数より1回多く回り、最後のfgets()はfalseを返します。

出力:

string(2) "a
"
string(2) "b
"
string(2) "c
"
bool(false)
4 rounds for 3 lines

その余分なfalseは、レポートの空の行や配列の空の要素になってしまいます。このページのほかのすべての例のように、読み込みそのものでループしましょう:while (($line = fgets($fh)) !== false)。

よくある質問

PHPのfopenのモードには何がありますか?

rは読み込み、wはファイルを空にしてから書き込み、aは末尾に書き込み、xは新しいファイルを作り存在すれば失敗、cは空にせずに書き込みます。+を付ける(r+、w+、a+、x+、c+)と読み書きの両方ができます。rとr+以外のすべてのモードは、ファイルがなければ作ります。

fopenのwとaの違いは何ですか?

wは開いた時点でファイルを空にし、先頭から書き込みます。aは中身を保ち、fseek()のあとでもすべてのfwrite()がファイルの末尾に行くので、ログファイル向けのモードです。

PHPでCSVファイルを読み込むには?

fopen($path, 'r')で開き、ループの中でfgetcsv()を呼びます:while (($row = fgetcsv($fh, null, ',', '"', '')) !== false) { ... }。呼び出しごとに1行をフィールドの配列で返し、引用符や中に含まれるカンマも処理してくれます。

PHPでfcloseを呼ぶ必要はありますか?

PHPはスクリプトの終わりに開いているすべてのファイルを閉じるので、短いスクリプトならなくても動きます。それでも、使い終わったらすぐにfclose()を呼びましょう。バッファされた書き込みをフラッシュし、flock()で取ったロックを解放し、長く動くスクリプトでファイルハンドルが尽きるのを防ぎます。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める