PHP Programming/Files

出典: フリー教科書『ウィキブックス(Wikibooks)』

ファイルの処理はどんなプログラミング言語にとっても重要なことであり、PHPも例外ではない。ファイルを使う理由が何であれ、PHPはいくつかの関数でそれを可能にしている。なお、このページではPHP/入門の読了を前提としている。

ディレクトリ[編集]

現在のディレクトリを表示する: dirname().

ディレクトリを変更する: chdir().

ディレクトリを作成する: mkdir().

fopen()fclose()[編集]

fopen()はファイル処理の基本である。この関数はファイルを(指定されたモードで)開き、ファイルハンドル(ポインタ)を返す。このハンドルを使うことで、ファイルを読み込んだり、書き込んだりすることができ、それが終わるとfclose()関数でファイルを閉じる。

<?php
$handle = fopen('data.txt', 'r'); // ファイルを読み込むために開く
fclose($handle); // ファイルを閉じる
?>

上記の例では'r'モードを指定して、「読み込む」ために開くことを示している。fopen()で使えるモードの一覧はPHPのマニュアルを参照の事。

上記はファイルを開く、閉じるの手順であるが、何か役に立つことをするためにはfread()fwrite()関数を知る必要がある。

PHPスクリプトの実行が終了すると、開かれたファイルは全て自動的に閉じられる。そのため、厳密にはファイルを開いた後にそれを閉じる必要はないが、ファイルをきちんと閉じることはプログラミングにおける良い慣習とされている。

ファイルを読み込む[編集]

ファイルの読み込みにはいくつかの方法がある。ファイルの内容を一気に読み込みたい場合はfile_get_contents()関数を、内容を行ごとに配列に格納したい場合はfile()関数を使う。ファイルの読み込みを自分で制御したい場合はfread()関数を使うことができる。

これらの関数は一般的にはお互いの間で互換性がある。file_get_contents()file()ではファイルを予めfopen()で開いておく必要も読み込んだ後にfclose()で閉じる必要もなく、ファイルを一度だけ使う場合などで役に立つ。より多くの作業を必要としている場合、fopen()fread()fwrite()fclose()を使うほうが良い。

file_get_contents()を使った例

コード:

<?php
$contents = file_get_contents('data.txt');
echo $contents;
?>

出力:

これはdata.txtの内容です
この関数はファイルの内容全てを文字列に読み込む。その後、ほかの文字列と同じように処理することができる。
file()を使った例

コード:

<?php
$lines = file('data.txt');
foreach($lines as $Key => $line) {
	$lineNum = $Key + 1;
	echo "Line $lineNum: $line";
}
?>

出力:

Line 1: ファイルの1行目
Line 2: ファイルの2行目
Line 3: ファイルの4行目だと言ったら嘘になる
この関数はファイルの内容全てを配列に読み込む。配列の値はそれぞれファイルの1行となる。
fread()を使った例

コード:

<?php
$handle = fopen('data.txt', 'r');
$string = fread($handle, 64);
fclose($handle);

echo $string;
?>

出力:

I am the first 64 bytes of data.txt (if it was ASCII encoded). I
この関数は指定されたバイト数をファイルから読み込み、それを文字列として返す。多くの場合、file_get_contents()とfile()のほうが良いが、この関数が必要な場合もある。

このように、上記の3関数を使うことでファイルを簡単に読み込み、処理に適するデータ型に変換することができる。下記の例は上記の関数の互換性を示すが、興味がない場合はスキップして次の節に移ることもできる。

<?php
$file = 'data.txt';

function detectLineEndings($contents) {
	if(false !== strpos($contents, "\r\n")) return "\r\n";
	else if(false !== strpos($contents, "\r")) return "\r";
	else return "\n";
}

/* file_get_contents($file)と同等だが、より遅い */
$handle = fopen($file, 'r');
$contents = fread($handle, filesize($file));
fclose($handle);

/* file($file)と同等だが、改行記号に留意する必要がある。
Windowsは\r\nを、Macintoshは\rを、Unixは\nを使っており、File($file)では自動的にチェックするが、
freadとfile_get_contentsは自動的にチェックしない */
$lineEnding = detectLineEndings($contents);
$contents = file_get_contents($file);
$lines = explode($lineEnding, $contents);

/* file_get_contents($file)と同等 */
$lines = file($file);
$contents = implode("\n", $lines);

/* エンコードがASCIIの場合、fread($file, 64)と同じ */
$contents = file_get_contents($file);
$string = substr($contents, 0, 64);
?>

ファイルに書き込む[編集]

ファイルへの書き込みはfwrite()関数で行われる(ファイルの開き閉じにfopen()fclose()を使う必要はある)。読み込みと違い、書き込みにはそれほど多くのオプションがないが、PHP 5ではfile_put_contents()が導入され、書き込みの手順をやや簡素化することができる。この関数はわかりやすいため、PHP 5の節でのみ説明される。

ファイルへの書き込みに使われるオプションは使える関数のバラエティではなく、ファイルを開くときに使えるモードにある。ファイルのモードはfopen()で指定することができ、うちファイルへの書き込みを許可するモードは3つある。'w'モードではファイルの内容を削除するため、書き込んだ内容がそのままファイルに記載される内容となる。'a'モードでは元からファイルにある内容を残し、書き込んだ内容は元からある内容の後に記載される。'x'モードはファイルが開かれる時点でまだ存在しない場合のみ成功する。これらのモードはファイルが存在しない場合、その作成を試みるが、'r'モードではファイルの作成は行われない。

'w'モードを使った例

コード:

<?php
$handle = fopen('data.txt', 'w'); // ファイルを開いてその内容を削除する
$data = "I am new content\nspread across\nseveral lines.";
fwrite($handle, $data);
fclose($handle);

echo file_get_contents('data.txt');
?>

出力:

I am new content
spread across
several lines.
'a'モードを使った例

コード:

<?php
$handle = fopen('data.txt', 'a'); // ファイルを追加書き込みモードで開く
$data = "\n\n新しい内容";
fwrite($handle, $data);
fclose($handle);

echo file_get_contents('data.txt');
?>

出力:

元々の内容

新しい内容
'x'モードを使った例

コード:

<?php
$handle = fopen('newfile.txt', 'x'); // ファイルが存在しない場合のみ、書き込みモードで開く
$data = "はじめての内容です";
fwrite($handle, $data);
fclose($handle);

echo file_get_contents('newfile.txt');
?>

出力:

はじめての内容です

上記の例で示されたモードのうち、'w'と'a'が最もよく使われるが、書き込みの手順はほぼ同じである。

読み書きの両方が必要な場合[編集]

fopen()でファイルを開いて読み込むことと書き込むことの両方を行いたい場合、モードの後ろに'+'を追記するだけでよい。例えば、ファイルの読み込みには'r'モードを使うが、書き込むことも必要な場合は'r+'モードを使う。同じように'w+'モードでも読み書きができるが、ファイルに元からある内容は削除される。詳しい説明はfopen()を参照のこと。

エラー検出[編集]

エラー検出はどんなプログラミングにとっても大事なことであるが、PHPでファイルを処理するときは特に大事である。エラー検出が必要な理由は主にファイルシステムに由来する。現在使われているウェブサーバーはUnixベースのものが大半なので、PHPでウェブアプリケーションを開発している場合、ファイルパーミッションにも留意しなければならない。PHPにファイルを読み込む権限がない場合、エラーになってしまう。また、(当然のことだが)ファイルが存在するかどうかも重要である。例えば、ファイルの読み込みを試みるとき、まずファイルが存在することを確かめる必要がある。一方、'x'モードでファイルを作成して書き込む場合ではファイルが存在しないことを確かめる必要がある。

かいつまんで言うと、コードを書いてファイルを処理する場合、常に最悪な場合を予想すべきである。例えば、ファイルが存在しない場合や読み書きのパーミッションがない場合を想定すべきである。大半の場合ではユーザーにファイルのパーミッションを変更して、PHPスクリプトが処理を行えるようにするよう求めるが、一部の場合では代替となる処理に変更することもできる。

エラー処理は主に2つの方法で行われる。1つは'@'演算子(エラー制御演算子)を使ってエラーを出力させず、続いてファイル関数の戻り値がfalseであるかを検査する方法である。もう1つはfile_exists()is_readable()is_writable()などの関数を使う方法である。

'@'演算子を使った例
<?php
$handle = @ fopen('data.txt', 'r');
if(!$handle) {
	echo 'PHPにファイル読み込みのパーミッションがないか、ファイルが存在しない。';
} else {
	$string = fread($handle, 64);
	fclose($handle);
}

$handle = @ fopen('data.txt', 'w'); // 'a'モードでも同じ
if(!$handle) {
	echo 'PHPにファイルへの書き込みパーミッションがないか、現在のディレクトリにファイルを作成するパーミッションがない。';
} else {
	fwrite($handle, '書き込みテスト');
	fclose($handle);
}

$handle = @ fopen('data.txt', 'x');
if(!$handle) {
	echo 'ファイルがすでに存在するか、PHPに現在のディレクトリにファイルを作成するパーミッションがない。';
} else {
	fwrite($handle, '書き込みテスト');
	fclose($handle);
}
?>
上記で示されているように、'@'演算子は主にfopen()関数とともに使われる。ほかの場合でも使えるが、より遅いことが多い。
検査用関数を使った例
<?php
$file = 'data.txt';

if(!file_exists($file)) {
	// 内容がないので、読み込んでも仕方がない
	$contents = '';
	
	// しかし、代わりにファイルを作成したほうがいいかもしれない
	$handle = @ fopen($file, 'x'); // ここでもエラーを検出する必要がある
	if(!$handle) {
		echo 'PHPに現在のディレクトリにファイルを作成するパーミッションがない。';
	} else {
		fwrite($handle, '書き込みテスト');
		fclose($handle);
	}
} else {
	// ファイルが存在するので、読み込みを試みる
	if(is_readable($file)) {
		$contents = file_get_contents($file);
	} else {
		echo 'PHPにファイル読み込みのパーミッションがない。';
	}
}

if(file_exists($file) && is_writable($file)) {
	$handle = fopen($file, 'w');
	fwrite($handle, '書き込みテスト');
	fclose($handle);
}
?>

最後の例にみられるように、エラー検出を行うことで、プログラムが強固になる。このプログラムは大半の場合によって柔軟に対応を変えることができる。

改行記号[編集]

改行記号はファイルを読み込む節でも軽く触れられたが、ファイル処理ではそれに留意することが大事である。改行記号とはプログラムに改行を行うよう指示する特別な記号である。例えば、Windowsのメモ帳アプリでは改行の直前に\r\nの記号がある場合のみ、次の行に移動する(右端を折り返す設定でも改行が行われる)。

Windowsでテキストファイルを作成する場合、改行記号に\r\nが使われると予想される。同じように、より古いMacintosh(Mac OS 9以下)で作成した場合は改行記号が\rになり、UnixベースのMac OS XやGNU/Linuxでは\nになる。

改行記号が重要である理由はなんでしょうか。例えば、file_get_contents()でファイルの内容を文字列に読み込む場合、改行記号もそのまま読み込まれる。それが処理を阻害する場合もあるので、下記のように改行記号を除去することができる。

<?php
$string = str_replace(array("\n", "\r"), '', $string);
?>

また、ファイルに内容を追加するとき、改行記号を元からある内容と合わせたい場合がほとんどなので、下記のdetectLineEndings()関数で改行記号を調べることができる。

<?php
function detectLineEndings($string) {
	if(false !== strpos($string, "\r\n")) return "\r\n";
	else if(false !== strpos($string, "\r")) return "\r";
	else return "\n";
}
?>

しかし、大半の場合では改行記号がファイル内に存在して、PHPスクリプトもそれに合わせる必要があると頭の片隅に留める程度で十分である。

バイナリ安全[編集]

これまで使われたテキストはASCIIやUTF-8のようなプレーンテキスト形式でエンコードされている。しかし、画像や実行ファイルなどプレーンテキスト形式以外のファイルも多い。このようなファイルを処理する場合、ファイル関数が「バイナリ安全」(binary-safe)でなければならない。以前のPHPではモードに'b'を追記してファイルをバイナリファイルであると明示する必要があり、それがなされていない場合は予想外の結果となる。しかし、およそPHP 4.3以降、PHPはファイルがテキストファイルかバイナリファイルかを検出することができ、モードで明示する必要がなくなった。

バイナリデータの処理はプレーンテキスト処理と大きく違い、このページではカバーできないが、このような差が存在することを知るのは大事である。

シリアライズ[編集]

シリアライズはデータを特定のフォーマットに変換して、後で元に戻せるようにするテクニックである。例えば、配列を文字列に変換してそれを保存することで、そのデータは後に配列に変換して再び使用することができる。

シリアライズは便利なテクニックであり、それ自体が一章に値するほどの内容であるが、データベースが使えないときはファイルに保存することが多いので、ここでも言及している。また、スクリプトの状態を保存したり、データをキャッシュしてより素早くアクセスできたりすることにも使われており、これらの目的にはファイルの方が適任である。

PHPにおけるシリアライズはserialize()unserialize()関数で行うことができ、下記がその一例である。

ユーザーデータをファイルに保存して、後で取り出す例。

コード:

<?php
/* このスクリプトではデータをファイルに保存している */
$data = array(
	'id' => 114,
	'first name' => 'Foo',
	'last name' => 'Bartholomew',
	'age' => 21,
	'country' => 'England'
);
$string = serialize($data);

$handle = fopen('data.dat', 'w');
fwrite($handle, $string);
fclose($handle);

/* その後、データをファイルから取り出してそれを出力する */
$string = file_get_contents('data.dat');
$data = unserialize($string);

$output = '';
foreach($data as $key => $datum) {
	$field = ucwords($key);
	$output .= "$field: $datum\n";
}

echo $output
?>

出力:

Id: 114
First Name: Foo
Last Name: Bartholomew
Age: 21
Country: England

PHP 5[編集]

PHP 5ではファイル関連の関数が1つ導入されている。それはfile_put_contents()のことであり、ファイル書き込みの新しい方法である。

PHP 4でファイルに書き込む例、及びPHP 5でfile_put_contents()関数を使った例
<?php
$file = 'data.txt';
$content = 'New content.';

// PHP 4、ファイルの内容を削除した後に書き込む
$handle = fopen($file, 'w');
fwrite($handle, $content);
fclose($handle);

// PHP 5
file_put_contents($file, $content);

// PHP 4、ファイルに追記する
$handle = fopen($file, 'a');
fwrite($handle, $content);
fclose($handle);

// PHP 5
file_put_contents($file, $content, FILE_APPEND);
?>
file_put_contents()はファイルが存在しない場合に作製を試み、またバイナリ安全でもある。なお、'x'モードと同等の機能は提供されていない。

ファイルを繰り返し操作する場合を除き、file_put_contents()のほうがfopen()より優先すべきである。下記はPHP 4でfile_put_contents()の挙動を再現する方法である。

<?php
if(!function_exists('file_put_contents')) {
	function file_put_contents($file, $data, $append = false) {
		if(!$append) $mode = 'w';
		else $mode = 'a';
		
		$handle = @ fopen($file, $mode);
		if(!$handle) return false;
		
		$bytes = fwrite($handle, $data);
		fclose($handle);
		
		return $bytes;
	}
}
?>