[PHP]fstat関数の詳細解説 – ファイル情報を取得するための強力ツール

PHP

PHPのfstat関数について解説します。この関数は前回解説したfsockopenの仲間とも言える、ファイル操作に関する重要な関数です。

fstat関数の基本

fstat関数は、オープンされたファイルポインタに関する情報を取得するためのPHP関数です。基本的な構文は以下の通りです:

array fstat(resource $stream)

この関数は、引数として渡されたファイルハンドル(ストリームリソース)の統計情報を含む連想配列を返します。

返り値の内容

fstat関数が返す配列には、以下のような情報が含まれます:

キー説明
devデバイス番号
inoiノード番号
mode保護モード
nlinkハードリンクの数
uidファイル所有者のユーザーID
gidファイル所有者のグループID
rdevデバイスタイプ(特殊ファイルの場合)
sizeファイルサイズ(バイト単位)
atime最終アクセス時刻(UNIX タイムスタンプ)
mtime最終更新時刻(UNIX タイムスタンプ)
ctimeiノード変更時刻(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のファイル操作において非常に有用なツールです。ファイルサイズ、更新日時、権限など、ファイルに関する詳細な情報を取得できます。

この関数の主な利点は:

  1. オープン済みファイルに対して使用できる – すでに処理中のファイルに対して追加のオープン操作なしに情報を取得できます
  2. 詳細な情報にアクセスできる – 単純なサイズや日時だけでなく、iノード情報やブロックサイズなど低レベルの情報も取得できます
  3. 効率的 – すでにオープンしているファイルハンドルを再利用するため、効率的です

ファイル操作を行うPHPアプリケーションでは、fstatを活用することで、より高度なファイル管理機能を実装できます。特に大量のファイル処理や、ファイルの状態監視などのシナリオで威力を発揮します。

タイトルとURLをコピーしました