目次
fopen関数でファイルの読み書きを行う
PHPでfopen関数を使用したファイルの読み書き込み操作の基本を紹介します。
環境:PHP 7.4.8
ファイル操作の基本的な流れ
fopen関数を使用したファイル操作の基本的な流れは読み込み、書き込み時共に以下のようになります。
ファイルを開く (fopen)
↓
ファイルをロックする (flock)
↓
読み込み / 書き込み
↓
ファイルのロックを解除する
↓
ファイルを閉じる (fclose)
ファイルを開く(fopen)
読み書きどちらの場合も最初にfopen関数で対象のファイルを開き、操作が終わったらfclose関数でファイルを閉じるまでが一連の操作となります。
fopenのモード指定
fopen関数は以下のように記述し、実行します。
fopen(ファイルパス, モード)
- 第一引数: 対象ファイルの場所を絶対パスまたは相対パスで指定
- 第二引数: ファイルを開くモードを指定
第二引数に指定するモードは下図のように10種類存在します。
モード | 読み込み/書き込み | ファイルが存在しない場合 | ファイルが存在する場合 | ポインタの位置 |
---|---|---|---|---|
rb | 読み込み専用 | エラー | 開く | 先頭 |
r+b | 読み込み/書き込み | エラー | 開く | 先頭 |
wb | 書き込み専用 | 新規作成 | ファイルを空にして開く | 先頭 |
w+b | 読み込み/書き込み | 新規作成 | ファイルを空にして開く | 先頭 |
ab | 書き込み専用(追記) | 新規作成 | 開く | 終端 |
a+b | 読み込み/書き込み(追記) | 新規作成 | 開く | 終端 |
xb | 書き込み専用 | 新規作成 | エラー | 先頭 |
x+b | 読み込み/書き込み | 新規作成 | エラー | 先頭 |
cb | 書き込み専用 | 新規作成 | 開く | 先頭 |
c+b | 読み込み/書き込み | 新規作成 | 開く | 先頭 |
種類別に読み書きの目的やファイル作成時の挙動が異なりますが、基本的な用途として主に以下がよく使用されます。
rb 読み込み
wb 書き込み (上書き)
ab 書き込み (追記)
それぞれ「r+b」のように+記号をつけることにより、読み書きどちらも行えるようになります。
bって何?
全てのモードについている「b」の文字ですがこちらは「バイナリモード」を表し、
対象がテキストデータであった場合も互換性を考慮し基本的にはつけておくことが推奨されています。
(bを省いていても基本的には開くことが出来る)
ポインタって何?
ファイルの中で現在指している位置のこと
(fopenで指定するモードによって先頭か終端かのどちらかになる)
flock関数による排他制御
操作の途中に登場するファイルのロックは排他制御といい、同じファイルに対して複数のユーザーから同時にアクセスされる可能性がある場合にデータの整合性を保つため、先にファイルを開いたユーザーがそのファイルを閉じるまでは他のユーザーからは操作出来ないようにする仕組みとなります。
排他制御はflock関数を使って行い、読み込み時と書き込み時でそれぞれ共有ロック、排他ロックという方法をモードの指定によって使い分けます。
対象の操作 | ロックの種類 | flock関数のモード | ロックの動作 |
---|---|---|---|
読み込み | 共有ロック | LOCK_SH | 他のユーザからは読み込みのみ許可する |
書き込み | 排他ロック | LOCK_EX | 他のユーザからは読み込みも書き込みも許可しない |
操作完了時にファイルを閉じる(fclose)
ファイルへ読み書きの操作が完了したらfclose関数を実行して開いたファイルを閉じます。
こちらの操作を行わないと目的の操作完了後もファイルが開いたままになってしまい、他のプロセスで操作することが出来なくなってしまう場合があります。
以下、fopenを組み合わせた具体的な実装方法を読み書き操作別に解説していきます。
ファイルからデータを読み込む(SAMPLE)
fgets関数を使用してファイルからデータを読み込みます。
fgetsはテキストデータから1行の単位でデータを取得する関数で、下記のようにロジックを組むことで全行分のテキストを読み込むことが出来ます。
<?php
$filepath = 'data.txt';
if (!$fp = fopen($filepath, 'rb')) {
echo 'ファイルが開けませんでした。';
return;
}
if (flock($fp, LOCK_SH) == false) {
echo 'ファイルがロック出来ませんでした。';
return;
}
$data = null;
while (!feof($fp)) {
$data[] = fgets($fp);
}
flock($fp, LOCK_UN);
fclose($fp);
foreach ($data as $line) {
echo $line . '<br>';
}
ここでは「読み込みのみ」としますので、fopen関数のモード「r」で実行してファイルを開きます。
ファイルを開くことに成功するとfopen関数はファイルリソースを返すのでそれを対象に共有ロックをかけて、データ取得操作を行います。
ファイルを開けなかった場合、fopen関数はfalseを返すので開けなかった場合の処理を分岐することが出来ます。
繰り返しのwhile文の条件にしている、feof関数でファイルポインタが行の終端に達しているかどうかを判定し、終端に達するまで各行ごとのデータを繰り返し配列に格納していきます。
データの読み込みが完了したらモード「LOCK_UN」でflock関数を実行してファイルのロックを解除し、fclose関数を実行して開いているファイルを閉じて処理を終了します。
ファイルへデータを書き込む(SAMPLE)
ファイルへ書き込みを行うためにfwrite関数を使います。
以下のサンプルではフォームから入力された値をテキストファイルの末尾に追加します。
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = null;
if ($_POST['text'] == null) {
$error = '入力してください。';
} else {
$filepath = 'data.txt';
if (!$fp = fopen($filepath, 'ab')) {
$error = 'ファイルが開けませんでした。';
} elseif (flock($fp, LOCK_EX) == false) {
$error = 'ファイルがロック出来ませんでした。';
} else {
fwrite($fp, $_POST['text'] . "\n");
flock($fp, LOCK_UN);
fclose($fp);
header('Location: /');
}
}
if ($error) {
echo $error;
}
}
?>
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>fopen test</title>
</head>
<body>
<h1>write test</h1>
<form action="" method="POST">
<p>入力: <input type="text" name="text"></p>
<p><button type="submit">送信</button></p>
</form>
</body>
</html>
今回は「書き込み(追記)のみ」としたいのでモード「a」を指定してfopen関数を実行します。
前述のファイル読み込み操作と基本的な流れは同じですが、flock関数には排他ロックの「LOCK_EX」を指定します。
書き込みごとに改行したいので、書き込みデータの末尾に改行コード(バックスラッシュエヌ)を付けます。
(補足)fflush関数について
書き込み操作においてfwriteの直後で、fflush関数というバッファに溜められた書き込みデータをファイルに出力(フラッシュ)する機能を使う事が出来ます。
fwrite($fp, $_POST['text']."\n");
fflush($fp);
連続した大量の書き込み処理時、即座に結果を確認したい場合などに用いられる機能のようですが、上記サンプルのケースのようにファイルへの入力データに改行文字を含んでいる場合は自動的にバッファを開放する仕様となっているため、実行の必要はありません。
ファイル書き込み時のパーミッション(権限)を設定する
書き込み操作を行う場合はPHPを実行するユーザ(Webサーバ)が対象ファイルの書き込み権限を持っていないと実行することが出来ませんのでファイルに対してあらかじめパーミッション設定を行っておく必要があります。
また、書き込み対象ファイルがまだ存在していないとき、モード「a」はファイルを新規作成しますが、その場合ファイルを格納するディレクトリに対して実行&書き込み権限がないといけませんのでそちらも設定しておきます。
今回はシンプルに全ユーザ、グループにフルアクセス(読み書き実行)とします。
chmodコマンドでパーミッション設定をする
chmod -R 777 ディレクトリパス
データの書き込み、読み込みどちらも行う (SAMPLE)
サンプルでは読み書き可能なモードでファイルを開き、
フォームから入力したデータの書き込みとデータ一覧(直後の書き込みも含む)の読み込みを順に行います。
<?php
$filepath = 'data.txt';
$fp = null;
$error = null;
if (!$fp = fopen($filepath, 'a+b')) {
$error = 'ファイルが開けませんでした。';
} elseif (!flock($fp, LOCK_EX)) {
$error = 'ファイルがロック出来ませんでした。';
}
if (!$error && $_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_POST['text'] == null) {
$error = '入力してください';
} else {
fwrite($fp, $_POST['text'] . "\n");
rewind($fp);
header('Location: /');
}
}
if ($error) {
echo $error;
}
$data = [];
if ($fp) {
while (!feof($fp)) {
$data[] = fgets($fp);
}
flock($fp, LOCK_UN);
fclose($fp);
}
?>
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>read write test</title>
</head>
<body>
<h1>read write test</h1>
<ul>
<?php foreach ($data as $line) : ?>
<?php if (!empty($line)) : ?>
<li><?= $line ?></li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<form action="" method="POST">
<p>入力: <input type="text" name="text"></p>
<p><button type="submit">送信</button></p>
</form>
</body>
</html>
モードを読み書き可の「a+b」としてfopenを実行し、リクエストがPOSTの時のみ書き込み処理をする仕組みとなっています。
rewind関数でポインタを戻す
fopen関数のモード「a+b」でファイルを開いた場合、ポインタはファイルの終端となるのでそのまま処理を進めてしまうと以降のfgets関数で読み込めるデータが無く、何も表示する事が出来ません。
fwriteでデータを書き込んだ後にrewind関数を実行し、終端の位置のままとなっているポインタをファイルの先頭に戻すことにより、以降の処理でファイルの先頭からデータを読み込む事が可能となります。