PHPのfstat
関数について解説します。この関数は前回解説したfsockopen
の仲間とも言える、ファイル操作に関する重要な関数です。
fstat関数の基本
fstat
関数は、オープンされたファイルポインタに関する情報を取得するためのPHP関数です。基本的な構文は以下の通りです:
array fstat(resource $stream)
この関数は、引数として渡されたファイルハンドル(ストリームリソース)の統計情報を含む連想配列を返します。
返り値の内容
fstat
関数が返す配列には、以下のような情報が含まれます:
キー | 説明 |
---|---|
dev | デバイス番号 |
ino | iノード番号 |
mode | 保護モード |
nlink | ハードリンクの数 |
uid | ファイル所有者のユーザーID |
gid | ファイル所有者のグループID |
rdev | デバイスタイプ(特殊ファイルの場合) |
size | ファイルサイズ(バイト単位) |
atime | 最終アクセス時刻(UNIX タイムスタンプ) |
mtime | 最終更新時刻(UNIX タイムスタンプ) |
ctime | iノード変更時刻(UNIX タイムスタンプ) |
blksize | ファイルシステムI/Oのブロックサイズ |
blocks | ファイルに割り当てられた512バイトブロックの数 |
また、数値インデックスでもアクセスできます(0から12まで)。
基本的な使用例
ファイルの情報を取得する例:
<?php
// ファイルをオープン
$file = fopen('example.txt', 'r');
// ファイルの統計情報を取得
$fileStats = fstat($file);
// 情報の表示
echo "ファイルサイズ: " . $fileStats['size'] . " バイト\n";
echo "最終更新時刻: " . date('Y-m-d H:i:s', $fileStats['mtime']) . "\n";
echo "ファイル権限: " . decoct($fileStats['mode'] & 0777) . "\n";
// ファイルを閉じる
fclose($file);
?>
実践的な活用例
1. ファイルの変更監視
<?php
function hasFileChanged($filename, &$lastMtime) {
if (!file_exists($filename)) {
return false;
}
$fp = fopen($filename, 'r');
$stat = fstat($fp);
fclose($fp);
if ($lastMtime === null) {
$lastMtime = $stat['mtime'];
return true; // 初回は変更されたとみなす
}
if ($stat['mtime'] > $lastMtime) {
$lastMtime = $stat['mtime'];
return true; // ファイルが変更された
}
return false; // 変更なし
}
// 使用例
$lastModified = null;
$filename = 'config.json';
// 定期的に実行される処理
if (hasFileChanged($filename, $lastModified)) {
echo "ファイルが変更されました。設定を再読み込みします。\n";
// 設定再読み込み処理
} else {
echo "ファイルに変更はありません。\n";
}
?>
2. ディスク使用量の分析
<?php
function analyzeDirectory($dir, &$totalSize, &$fileCount, &$dirCount) {
if (!is_dir($dir)) {
return false;
}
$dirCount++;
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
if ($file == '.' || $file == '..') {
continue;
}
$path = $dir . '/' . $file;
if (is_dir($path)) {
analyzeDirectory($path, $totalSize, $fileCount, $dirCount);
} else {
$fileCount++;
$fp = fopen($path, 'r');
$stat = fstat($fp);
$totalSize += $stat['size'];
fclose($fp);
}
}
closedir($dh);
return true;
}
// 使用例
$totalSize = 0;
$fileCount = 0;
$dirCount = 0;
analyzeDirectory('/path/to/analyze', $totalSize, $fileCount, $dirCount);
echo "ディレクトリ数: $dirCount\n";
echo "ファイル数: $fileCount\n";
echo "合計サイズ: " . number_format($totalSize / 1024 / 1024, 2) . " MB\n";
?>
3. ファイルのロック状態確認
<?php
function isFileLocked($filename) {
// ファイルをオープン(排他的ロックを試みる)
$fp = fopen($filename, 'r');
if (!$fp) {
return true; // オープンできない場合はロックされているとみなす
}
// 非ブロッキングでロックを試みる
$locked = !flock($fp, LOCK_EX | LOCK_NB, $wouldBlock);
// ロックを解除
if (!$wouldBlock) {
flock($fp, LOCK_UN);
}
// ファイル情報を取得
$stat = fstat($fp);
fclose($fp);
// wouldBlockがtrueならファイルはロックされている
return $wouldBlock;
}
// 使用例
$filename = 'data.txt';
if (isFileLocked($filename)) {
echo "ファイルは現在ロックされています。\n";
} else {
echo "ファイルはロックされていません。\n";
}
?>
ファイル権限の詳細解析
<?php
function getFilePermissions($file) {
$fp = fopen($file, 'r');
$stat = fstat($fp);
fclose($fp);
$mode = $stat['mode'];
// ファイルタイプを判定
$type = 'unknown';
if (($mode & 0170000) === 0100000) {
$type = 'file';
} elseif (($mode & 0170000) === 0040000) {
$type = 'directory';
} elseif (($mode & 0170000) === 0120000) {
$type = 'link';
} elseif (($mode & 0170000) === 0060000) {
$type = 'block';
} elseif (($mode & 0170000) === 0020000) {
$type = 'char';
} elseif (($mode & 0170000) === 0140000) {
$type = 'socket';
} elseif (($mode & 0170000) === 0010000) {
$type = 'fifo';
}
// 権限文字列を構築(lsコマンドのような形式)
$permissions = '';
// ファイルタイプ
switch ($type) {
case 'file': $permissions .= '-'; break;
case 'directory': $permissions .= 'd'; break;
case 'link': $permissions .= 'l'; break;
case 'block': $permissions .= 'b'; break;
case 'char': $permissions .= 'c'; break;
case 'socket': $permissions .= 's'; break;
case 'fifo': $permissions .= 'p'; break;
default: $permissions .= '?';
}
// 所有者の権限
$permissions .= (($mode & 0000400) ? 'r' : '-');
$permissions .= (($mode & 0000200) ? 'w' : '-');
$permissions .= (($mode & 0000100) ? 'x' : '-');
// グループの権限
$permissions .= (($mode & 0000040) ? 'r' : '-');
$permissions .= (($mode & 0000020) ? 'w' : '-');
$permissions .= (($mode & 0000010) ? 'x' : '-');
// その他の権限
$permissions .= (($mode & 0000004) ? 'r' : '-');
$permissions .= (($mode & 0000002) ? 'w' : '-');
$permissions .= (($mode & 0000001) ? 'x' : '-');
return [
'type' => $type,
'octal' => sprintf('%04o', $mode & 0777),
'symbolic' => $permissions,
'uid' => $stat['uid'],
'gid' => $stat['gid'],
'size' => $stat['size'],
'atime' => date('Y-m-d H:i:s', $stat['atime']),
'mtime' => date('Y-m-d H:i:s', $stat['mtime']),
'ctime' => date('Y-m-d H:i:s', $stat['ctime'])
];
}
// 使用例
$fileInfo = getFilePermissions('example.txt');
print_r($fileInfo);
?>
fstatとファイルシステム関数の比較
PHPには、ファイル情報を取得するための他の関数も存在します:
関数 | 説明 | 特徴 |
---|---|---|
fstat() | オープンされたファイルハンドルの情報を取得 | すでにオープンされたファイルに使用 |
stat() | ファイルパスからファイルの情報を取得 | ファイルオープンが不要 |
lstat() | シンボリックリンクの情報を取得 | リンク自体の情報を取得(リンク先ではない) |
filesize() | ファイルサイズのみを取得 | サイズだけが必要な場合に簡単 |
filemtime() | 最終更新時刻のみを取得 | 更新日時だけが必要な場合に簡単 |
それぞれの関数の使い分け:
<?php
// すでにオープンしているファイル
$fp = fopen('data.txt', 'r');
$info1 = fstat($fp);
fclose($fp);
// オープンしていないファイル
$info2 = stat('data.txt');
// シンボリックリンク
$info3 = lstat('link.txt');
// 単純なサイズ取得
$size = filesize('data.txt');
// 最終更新日時のみ取得
$lastModified = filemtime('data.txt');
?>
実用的なファイル管理クラス
<?php
class FileManager {
private $path;
private $handle;
private $lastStat;
public function __construct($path) {
$this->path = $path;
}
public function open($mode = 'r') {
if ($this->handle) {
$this->close();
}
$this->handle = fopen($this->path, $mode);
if (!$this->handle) {
throw new Exception("Could not open file: {$this->path}");
}
$this->updateStat();
return $this;
}
public function close() {
if ($this->handle) {
fclose($this->handle);
$this->handle = null;
}
return $this;
}
public function read($length = null) {
if (!$this->handle) {
$this->open('r');
}
if ($length === null) {
return stream_get_contents($this->handle);
} else {
return fread($this->handle, $length);
}
}
public function write($data) {
if (!$this->handle) {
$this->open('w');
}
$bytes = fwrite($this->handle, $data);
$this->updateStat();
return $bytes;
}
public function append($data) {
if (!$this->handle) {
$this->open('a');
}
$bytes = fwrite($this->handle, $data);
$this->updateStat();
return $bytes;
}
public function seek($offset, $whence = SEEK_SET) {
if (!$this->handle) {
$this->open('r+');
}
return fseek($this->handle, $offset, $whence);
}
public function lock($exclusive = true, $nonBlocking = false) {
if (!$this->handle) {
$this->open('r+');
}
$operation = $exclusive ? LOCK_EX : LOCK_SH;
if ($nonBlocking) {
$operation |= LOCK_NB;
}
return flock($this->handle, $operation);
}
public function unlock() {
if (!$this->handle) {
return false;
}
return flock($this->handle, LOCK_UN);
}
public function updateStat() {
if (!$this->handle) {
return false;
}
$this->lastStat = fstat($this->handle);
return $this->lastStat;
}
public function getSize() {
if (!$this->lastStat) {
$this->updateStat();
}
return $this->lastStat ? $this->lastStat['size'] : 0;
}
public function getLastModified() {
if (!$this->lastStat) {
$this->updateStat();
}
return $this->lastStat ? $this->lastStat['mtime'] : 0;
}
public function getPermissions($symbolic = false) {
if (!$this->lastStat) {
$this->updateStat();
}
if (!$this->lastStat) {
return false;
}
$mode = $this->lastStat['mode'];
if ($symbolic) {
$perms = '';
// ファイルタイプ
if (($mode & 0170000) === 0040000) {
$perms .= 'd'; // ディレクトリ
} else {
$perms .= '-';
}
// 所有者
$perms .= (($mode & 0000400) ? 'r' : '-');
$perms .= (($mode & 0000200) ? 'w' : '-');
$perms .= (($mode & 0000100) ? 'x' : '-');
// グループ
$perms .= (($mode & 0000040) ? 'r' : '-');
$perms .= (($mode & 0000020) ? 'w' : '-');
$perms .= (($mode & 0000010) ? 'x' : '-');
// その他
$perms .= (($mode & 0000004) ? 'r' : '-');
$perms .= (($mode & 0000002) ? 'w' : '-');
$perms .= (($mode & 0000001) ? 'x' : '-');
return $perms;
} else {
return sprintf('%04o', $mode & 0777);
}
}
public function isReadable() {
if (!$this->lastStat) {
$this->updateStat();
}
$mode = $this->lastStat['mode'];
$uid = $this->lastStat['uid'];
$gid = $this->lastStat['gid'];
// 現在のプロセスのユーザーID・グループID
$processUid = posix_getuid();
$processGid = posix_getgid();
// 所有者の場合
if ($processUid === $uid) {
return ($mode & 0000400) !== 0;
}
// グループメンバーの場合
if ($processGid === $gid) {
return ($mode & 0000040) !== 0;
}
// その他の場合
return ($mode & 0000004) !== 0;
}
public function isWritable() {
if (!$this->lastStat) {
$this->updateStat();
}
$mode = $this->lastStat['mode'];
$uid = $this->lastStat['uid'];
$gid = $this->lastStat['gid'];
// 現在のプロセスのユーザーID・グループID
$processUid = posix_getuid();
$processGid = posix_getgid();
// 所有者の場合
if ($processUid === $uid) {
return ($mode & 0000200) !== 0;
}
// グループメンバーの場合
if ($processGid === $gid) {
return ($mode & 0000020) !== 0;
}
// その他の場合
return ($mode & 0000002) !== 0;
}
public function __destruct() {
$this->close();
}
}
// 使用例
try {
$file = new FileManager('config.ini');
// ファイル情報の取得
$file->open();
echo "ファイルサイズ: " . $file->getSize() . " バイト\n";
echo "最終更新日時: " . date('Y-m-d H:i:s', $file->getLastModified()) . "\n";
echo "ファイル権限: " . $file->getPermissions() . " (" . $file->getPermissions(true) . ")\n";
// ファイルの読み書き
$content = $file->read();
echo "ファイル内容:\n$content\n";
$file->append("\n# Added on " . date('Y-m-d H:i:s') . "\n");
$file->close();
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
まとめ
fstat
関数はPHPのファイル操作において非常に有用なツールです。ファイルサイズ、更新日時、権限など、ファイルに関する詳細な情報を取得できます。
この関数の主な利点は:
- オープン済みファイルに対して使用できる – すでに処理中のファイルに対して追加のオープン操作なしに情報を取得できます
- 詳細な情報にアクセスできる – 単純なサイズや日時だけでなく、iノード情報やブロックサイズなど低レベルの情報も取得できます
- 効率的 – すでにオープンしているファイルハンドルを再利用するため、効率的です
ファイル操作を行うPHPアプリケーションでは、fstat
を活用することで、より高度なファイル管理機能を実装できます。特に大量のファイル処理や、ファイルの状態監視などのシナリオで威力を発揮します。