@ PHP

Filesystem functions:ファイルシステム関数

ファイルシステム関数は、ファイルシステム上のフォルダ(ディレクトリ)やファイルを操作するための関数です。フォルダ・ファイルの情報を取得する、ファイルの読み書きを行うなど、さまざまな機能を提供します。
PHP: ファイルシステム関数 – Manual

テキストファイルへの書き込み

file_01.phpは、アクセスログ情報をタブ区切りテキストの形式でテキストファイルに書き込むソースコードです。

具体的には、アクセス日時、アクセスしたスクリプトのパス、クライアントの種類、リンク元のアドレスといった情報を書き込みます。

file_01.php

<?php
// 書き込み内容を配列 $data にセット
$data[] = date('Y/m/d H:i:s');
$data[] = $_SERVER['SCRIPT_NAME'];
$data[] = $_SERVER['HTTP_USER_AGENT'];
$data[] = $_SERVER['HTTP_REFERER'];
// access.log を追記書き込みモードで開く
$file = fopen('access.log', 'ab') or die('ファイルを開けませんでした!');
// ファイルのロック
flock($file, LOCK_EX);
// ファイルの書き込み
fwrite($file, implode("\t", $data) . "\n");
// ファイルを閉じる
fclose($file);
print 'アクセスログを記録しました。';

ファイル書き込み基本手順の一連の流れは次のようになります。

  1. ファイルを開く
  2. ファイルをロックする
  3. ファイルに対して書き込みを行う
  4. ファイルを閉じる(ロックを解除する)

1. ファイルを開く

スクリプトからテキストファイルに書き込みを行う場合、まずテキストファイルを「開く」必要があります。

ファイルを開くには、fopen() を使います。

fopen() はファイルのオープンに成功すると戻り値としてファイルハンドルを返します。ファイルハンドルは、ファイルを操作するためのキーとなる情報で、resource 型に属する値です。

ファイルに対する読み書きを行う場合には、このファイルハンドラに対して行うことになります。

ファイルを開く際のオープンモードは、次のようなものがあります。用途に応じて使い分けてください。

fopen()関数で指定可能なモード
モード 許可可能な動作 開始ブックマークの位置 ファイルが存在しない場合
rb 読み込み ファイルの始め 警告を発行し、falseを返す
rb+ 読み込み、書き出し ファイルの始め 警告を発行し、falseを返す
wb 書き出し ファイルの始め 作成を試みる
wb+ 読み込み、書き出し ファイルの始め 作成を試みる
ab 書き出し ファイルの始め 作成を試みる
ab 書き出し ファイルの終わり 作成を試みる
ab+ 読み込み、書き出し ファイルの終わり 作成を試みる
xb 書き出し ファイルの始め 作成を試みる。存在していれば警告を発行し、falseを返す
xb+ 読み込み、書き出し ファイルの始め 作成を試みる。存在していれば警告を発行し、falseを返す

2. fopen() でのエラー処理

fopen() は、「指定されたファイルが存在しない」「ファイルに書き込む権限がない」などの理由でファイルが開けなっかた場合、戻り値として false を返すとともに、警告(warning)を発生します。

警告を発生させたくない場合には、エラー制御演算子 @ を fopen() の前につけることで、ファイルのオープンに失敗しても警告は発生しなくなります。

ただし、これだけではエラーが発生しても問題の箇所がわかりませんし、エラーがあった場合にはスクリプトの実行を継続するべきではありません。

そこで必要となるのが or die() という記述です。

下記のソースコードは filename を開く、開けなければ終了する。という記述になります。

example.php

$file = @fopen('filename', 'openmode') or die('ERR! message');

3. ファイルへの書き込み

開かれたファイル(ファイルハンドル)に対して書き込みを行うのは、fwite() の役割です。

fwite() にはエイリアスとして fputs() があります。エイリアスとは、名前が異なる、まったく同じ機能を持った関数のことで、fwite() を fputs() と書き換えても、同様に動作します。

4. ファイルのロック

ファイルへの書き込みそのものは fopen() fwite() fclose() によって簡単にできてしまいます。しかし、ここで注意すべき点があります。それは「ファイルへの同時書き込み」です。

PHP では、flock() を利用することで、直観的にファイルをロック・アンロックすることができます。

ロックモードとして指定できる値は次のとおりです。

利用可能なロックモード(flock() の引数)
定数 説明
LOCK_SH 共有ロック(同時に複数プロセスから読めて、書けない)
LOCK_EX 排他ロック(同時に1プロセスだけが読み書きできる)
LOCK_UN ロック開放
LOCK_NB ロックを確保できない時点ですぐにやめる非ブロック。(Windowsでは未対応)

タブ区切りテキストの読み込み

file_01.php で書き込んだアクセスログ(access.log)を読み込み、HTML にテーブルとして一覧表示してみましょう。

file_02.php

<table border="1">
  <tr>
    <th>アクセス日時</th>
    <th>スクリプト名</th>
    <th>ユーザエージェント</th>
    <th>リンク元のURL</th>
  </tr>
<?php
// ファイルを読み取り専用でオープン
$file = fopen('access.log', 'rb');
// ファイルを共有ロック
flock($file, LOCK_SH);
// 行単位でテキストを読み込み、タブ文字で分割
while ($line = fgetcsv($file, 1024, "\t")) {
    print '<tr>';
    // 成功した結果を順に出力
    foreach ($line as $value) {
        print '<td>' . $value . '</td>';
    }
    print '</tr>';
}
// ファイルをクローズ
fclose($file);
?>
</table>

fgetcsv() はタブ区切りのテキストやカンマ区切りのテキストなど、定型フォーマットのテキストを読み込む場合に便利な関数です。名前から受けるイメージとは異なり、CSVファイル(カンマ区切りのテキスト)の処理に特化した関数というわけではなく、特定の区切り文字を持ったテキストに汎用的に対応する関数です。

タブ区切りテキストの読み込み(別解)

file_02.php をfgets() と file() で書き換えてみましょう。

fgets()

file_03.php

<table border="1">
  <tr>
    <th>アクセス日時</th>
    <th>スクリプト名</th>
    <th>ユーザエージェント</th>
    <th>リンク元のURL</th>
  </tr>
<?php
// ファイルを読み取り専用でオープン
$file = fopen('access.log', 'rb');
// ファイルを共有ロック
flock($file, LOCK_SH);
// 行単位でテキストを読み込み、タブ文字で分割
while ($fline = fgets($file, 1024)) {
    $line = explode("\t", $fline);
    print '<tr>';
    // 成功した結果を順に出力
    foreach ($line as $value) {
        print '<td>' . $value . '</td>';
    }
    print '</tr>';
}
// ファイルをクローズ
fclose($file);
?>
</table>

この場合は、読み込んだ行データを explode() で分割処理する必要があります。

タブ区切りテキストを読み込む場合には fgetcsv() の方が便利ですが、特定の区切り文字を持たないテキストを順に読み込む場合には fgets() が適していると言えます。

file()

file_04.php は file_01.php を file() で書き換えたソースコードです。

file_04.php

<table border="1">
  <tr>
    <th>アクセス日時</th>
    <th>スクリプト名</th>
    <th>ユーザエージェント</th>
    <th>リンク元のURL</th>
  </tr>
<?php
// ファイルを読み取り専用でオープン
$file = file('access.log');
// 配列に格納された行を順に処理
foreach ($file as $fline) {
    // タブ文字で行単位のテキストを分割
    $line = explode("\t", $fline);
    print '<tr>';
    // 分割した結果を順に出力
    foreach ($line as $value) {
        print '<td>' . $value . '</td>';
    }
    print '</tr>';
}
?>
</table>

file() はファイルの内容をまとめて取得したい場合に便利ですが、巨大なテキストを処理する場合、メモリを大量に消費するので、fgets() fgetcsv() を優先して利用すべきです。

file_get_contents() でファイルを読み込む

ファイルの内容を(配列ではなく)文字列にまとめて読み込みたい場合には、file_get_contents() を使います。この関数にファイル名を渡すと、ファイル内に含まれる全てを文字列として返します。

file_05.php では、file_page_template.html を file_get_contents() を使って読み込み、str_replace() を使って修正してから、結果を出力します。

file_page_template.html

<html>

<head>
<title>{page_title}</title>
</head>

<body bgcolor="{color}">
  <h1>Hello, {name}</h1>
</body>

</html>

file_05.php

<?php
session_start();
$user = "user";
$_SESSION['username'] = $user;
?>

<?php
$page = file_get_contents('file_page_template.html');
$page = str_replace('{page_title}','Welcom',$page);
if (date('H' >= 12)) {
    $page = str_replace('{color}', 'blue', $page);
} else {
    $page = str_replace('{color}', 'green', $page);
}
$page = str_replace('{name}', $_SESSION['username'], $page);

print $page;

file_put_contents() でファイルに保存する

ファイルの内容と文字列への読み込みの反対は、文字列のファイルへの書き出しです。そして file_get_contents() の対になるのは file_put_contents() です。

file_06.phpでは、people.txt に文字列を書き込み保存します。

file_06.php

<?php
$file = 'people.txt';
// ファイルをオープンして既存のコンテンツを取得します
$current = file_get_contents($file);
// 新しい人物をファイルに追加します
$current .= "John Smith\n";
// 結果をファイルに書き出します
file_put_contents($file, $current);
print 'people.txt に新しい人物を書き込みました';