[PHP]stat関数を完全解説!ファイル情報を詳細に取得する方法

PHP

こんにちは!今回は、PHPの標準関数であるstat()について詳しく解説していきます。ファイルやディレクトリの詳細情報を取得できる、ファイルシステム操作に欠かせない関数です!

stat関数とは?

stat()関数は、ファイルやディレクトリの詳細情報を取得する関数です。

ファイルサイズ、パーミッション、所有者、タイムスタンプ、inode番号など、ファイルに関する13種類の情報を配列で返します。ファイル管理、監視、バックアップシステムなどで重要な役割を果たします!

基本的な構文

stat(string $filename): array|false
  • $filename: 情報を取得するファイルまたはディレクトリのパス
  • 戻り値: ファイル情報の配列、失敗時はfalse

戻り値の配列構造

$info = stat('file.txt');

/*
配列のインデックス:
[0] または ['dev']     => デバイス番号
[1] または ['ino']     => inode番号
[2] または ['mode']    => inode保護モード
[3] または ['nlink']   => リンク数
[4] または ['uid']     => 所有者のユーザーID
[5] または ['gid']     => 所有者のグループID
[6] または ['rdev']    => デバイスタイプ(inode デバイスの場合)
[7] または ['size']    => バイト単位のサイズ
[8] または ['atime']   => 最終アクセス時刻(Unixタイムスタンプ)
[9] または ['mtime']   => 最終更新時刻(Unixタイムスタンプ)
[10] または ['ctime']  => 最終変更時刻(Unixタイムスタンプ)
[11] または ['blksize'] => ファイルシステムI/Oのブロックサイズ
[12] または ['blocks']  => 割り当てられた512バイトブロックの数
*/

基本的な使用例

シンプルな情報取得

$file = 'test.txt';
$info = stat($file);

if ($info === false) {
    echo "ファイル情報の取得に失敗しました\n";
} else {
    echo "ファイルサイズ: {$info['size']} bytes\n";
    echo "最終更新: " . date('Y-m-d H:i:s', $info['mtime']) . "\n";
    echo "最終アクセス: " . date('Y-m-d H:i:s', $info['atime']) . "\n";
}

数値インデックスと文字列キー

$info = stat('file.txt');

// 数値インデックスでアクセス
echo "サイズ(数値): {$info[7]}\n";

// 文字列キーでアクセス(推奨)
echo "サイズ(文字列): {$info['size']}\n";

// 両方とも同じ値
var_dump($info[7] === $info['size']);  // true

パーミッションの取得

$info = stat('file.txt');

// モード(パーミッション)
$mode = $info['mode'];

// 8進数で表示
echo "パーミッション(8進数): " . decoct($mode & 0777) . "\n";

// ファイルタイプとパーミッションを分離
$perms = $mode & 0777;
echo "パーミッション: " . sprintf('%o', $perms) . "\n";

タイムスタンプの取得

$info = stat('file.txt');

// アクセス時刻
echo "最終アクセス: " . date('Y-m-d H:i:s', $info['atime']) . "\n";

// 更新時刻(内容の変更)
echo "最終更新: " . date('Y-m-d H:i:s', $info['mtime']) . "\n";

// 変更時刻(メタデータの変更)
echo "最終変更: " . date('Y-m-d H:i:s', $info['ctime']) . "\n";

// 現在時刻との差分
$now = time();
$age = $now - $info['mtime'];
echo "最終更新からの経過時間: {$age}秒\n";

ファイルタイプの判定

$info = stat('file.txt');
$mode = $info['mode'];

// ファイルタイプの判定
if (($mode & 0170000) === 0100000) {
    echo "通常のファイル\n";
} elseif (($mode & 0170000) === 0040000) {
    echo "ディレクトリ\n";
} elseif (($mode & 0170000) === 0120000) {
    echo "シンボリックリンク\n";
}

// または関数を使用
if (is_file('file.txt')) {
    echo "通常のファイル\n";
} elseif (is_dir('file.txt')) {
    echo "ディレクトリ\n";
} elseif (is_link('file.txt')) {
    echo "シンボリックリンク\n";
}

実践的な使用例

例1: ファイル情報解析ツール

class FileInfoAnalyzer {
    /**
     * ファイルの詳細情報を取得
     */
    public static function getInfo($file) {
        $stat = stat($file);
        
        if ($stat === false) {
            return null;
        }
        
        return [
            'path' => $file,
            'name' => basename($file),
            'size' => $stat['size'],
            'size_formatted' => self::formatBytes($stat['size']),
            'permissions' => self::formatPermissions($stat['mode']),
            'permissions_octal' => sprintf('%o', $stat['mode'] & 0777),
            'owner_uid' => $stat['uid'],
            'owner_gid' => $stat['gid'],
            'type' => self::getFileType($stat['mode']),
            'inode' => $stat['ino'],
            'links' => $stat['nlink'],
            'accessed' => $stat['atime'],
            'modified' => $stat['mtime'],
            'changed' => $stat['ctime'],
            'accessed_formatted' => date('Y-m-d H:i:s', $stat['atime']),
            'modified_formatted' => date('Y-m-d H:i:s', $stat['mtime']),
            'changed_formatted' => date('Y-m-d H:i:s', $stat['ctime'])
        ];
    }
    
    /**
     * バイトを人間が読みやすい形式に変換
     */
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }
    
    /**
     * パーミッションを文字列形式に変換
     */
    private static function formatPermissions($mode) {
        $perms = '';
        
        // ファイルタイプ
        if (($mode & 0170000) === 0040000) $perms .= 'd';
        elseif (($mode & 0170000) === 0120000) $perms .= 'l';
        else $perms .= '-';
        
        // 所有者
        $perms .= ($mode & 0400) ? 'r' : '-';
        $perms .= ($mode & 0200) ? 'w' : '-';
        $perms .= ($mode & 0100) ? 'x' : '-';
        
        // グループ
        $perms .= ($mode & 040) ? 'r' : '-';
        $perms .= ($mode & 020) ? 'w' : '-';
        $perms .= ($mode & 010) ? 'x' : '-';
        
        // その他
        $perms .= ($mode & 04) ? 'r' : '-';
        $perms .= ($mode & 02) ? 'w' : '-';
        $perms .= ($mode & 01) ? 'x' : '-';
        
        return $perms;
    }
    
    /**
     * ファイルタイプを取得
     */
    private static function getFileType($mode) {
        $type = $mode & 0170000;
        
        switch ($type) {
            case 0040000: return 'directory';
            case 0100000: return 'file';
            case 0120000: return 'symlink';
            case 0060000: return 'block_device';
            case 0020000: return 'character_device';
            case 0010000: return 'fifo';
            case 0140000: return 'socket';
            default: return 'unknown';
        }
    }
    
    /**
     * 複数ファイルの情報を取得
     */
    public static function analyzeMultiple($files) {
        $results = [];
        
        foreach ($files as $file) {
            $info = self::getInfo($file);
            if ($info !== null) {
                $results[] = $info;
            }
        }
        
        return $results;
    }
    
    /**
     * ファイル情報を表形式で表示
     */
    public static function displayInfo($file) {
        $info = self::getInfo($file);
        
        if ($info === null) {
            echo "ファイル情報の取得に失敗しました\n";
            return;
        }
        
        echo "=== ファイル情報: {$info['name']} ===\n";
        echo "パス: {$info['path']}\n";
        echo "タイプ: {$info['type']}\n";
        echo "サイズ: {$info['size_formatted']} ({$info['size']} bytes)\n";
        echo "パーミッション: {$info['permissions']} ({$info['permissions_octal']})\n";
        echo "所有者: UID {$info['owner_uid']}, GID {$info['owner_gid']}\n";
        echo "inode: {$info['inode']}\n";
        echo "リンク数: {$info['links']}\n";
        echo "最終アクセス: {$info['accessed_formatted']}\n";
        echo "最終更新: {$info['modified_formatted']}\n";
        echo "最終変更: {$info['changed_formatted']}\n";
    }
}

// 使用例
echo "=== ファイル情報解析 ===\n";

$file = '/tmp/test.txt';

// 詳細情報を表示
FileInfoAnalyzer::displayInfo($file);

// 情報を配列で取得
$info = FileInfoAnalyzer::getInfo($file);
if ($info) {
    echo "\nサイズ: {$info['size_formatted']}\n";
    echo "パーミッション: {$info['permissions']}\n";
}

例2: ファイル監視システム

class FileMonitor {
    private $watchList = [];
    
    /**
     * 監視対象にファイルを追加
     */
    public function addFile($file) {
        if (!file_exists($file)) {
            return false;
        }
        
        $stat = stat($file);
        
        $this->watchList[$file] = [
            'size' => $stat['size'],
            'mtime' => $stat['mtime'],
            'ctime' => $stat['ctime'],
            'mode' => $stat['mode'],
            'last_check' => time()
        ];
        
        return true;
    }
    
    /**
     * 変更をチェック
     */
    public function checkChanges() {
        $changes = [];
        
        foreach ($this->watchList as $file => $oldInfo) {
            if (!file_exists($file)) {
                $changes[$file] = ['type' => 'deleted'];
                continue;
            }
            
            $stat = stat($file);
            $fileChanges = [];
            
            // サイズの変更
            if ($stat['size'] !== $oldInfo['size']) {
                $fileChanges['size'] = [
                    'old' => $oldInfo['size'],
                    'new' => $stat['size']
                ];
            }
            
            // 内容の変更
            if ($stat['mtime'] !== $oldInfo['mtime']) {
                $fileChanges['modified'] = [
                    'old' => date('Y-m-d H:i:s', $oldInfo['mtime']),
                    'new' => date('Y-m-d H:i:s', $stat['mtime'])
                ];
            }
            
            // メタデータの変更
            if ($stat['ctime'] !== $oldInfo['ctime']) {
                $fileChanges['metadata'] = [
                    'old' => date('Y-m-d H:i:s', $oldInfo['ctime']),
                    'new' => date('Y-m-d H:i:s', $stat['ctime'])
                ];
            }
            
            // パーミッションの変更
            if ($stat['mode'] !== $oldInfo['mode']) {
                $fileChanges['permissions'] = [
                    'old' => sprintf('%o', $oldInfo['mode'] & 0777),
                    'new' => sprintf('%o', $stat['mode'] & 0777)
                ];
            }
            
            if (!empty($fileChanges)) {
                $changes[$file] = $fileChanges;
                
                // 情報を更新
                $this->watchList[$file] = [
                    'size' => $stat['size'],
                    'mtime' => $stat['mtime'],
                    'ctime' => $stat['ctime'],
                    'mode' => $stat['mode'],
                    'last_check' => time()
                ];
            }
        }
        
        return $changes;
    }
    
    /**
     * 監視中のファイル一覧を取得
     */
    public function getWatchList() {
        return array_keys($this->watchList);
    }
    
    /**
     * 監視からファイルを削除
     */
    public function removeFile($file) {
        unset($this->watchList[$file]);
    }
}

// 使用例
echo "=== ファイル監視システム ===\n";

$monitor = new FileMonitor();

// 監視対象を追加
$monitor->addFile('/tmp/test.txt');
$monitor->addFile('/tmp/config.php');

echo "監視中のファイル:\n";
foreach ($monitor->getWatchList() as $file) {
    echo "  - {$file}\n";
}

// 少し待機してから変更をチェック
sleep(1);

// ファイルを変更(テスト用)
// file_put_contents('/tmp/test.txt', 'new content');

// 変更をチェック
$changes = $monitor->checkChanges();

if (empty($changes)) {
    echo "\n変更はありません\n";
} else {
    echo "\n検出された変更:\n";
    foreach ($changes as $file => $fileChanges) {
        echo "\n{$file}:\n";
        foreach ($fileChanges as $type => $change) {
            if ($type === 'deleted') {
                echo "  - ファイルが削除されました\n";
            } else {
                echo "  - {$type}: {$change['old']} → {$change['new']}\n";
            }
        }
    }
}

例3: ファイル比較ツール

class FileComparator {
    /**
     * 2つのファイルの統計情報を比較
     */
    public static function compare($file1, $file2) {
        $stat1 = stat($file1);
        $stat2 = stat($file2);
        
        if ($stat1 === false || $stat2 === false) {
            return null;
        }
        
        return [
            'same_size' => $stat1['size'] === $stat2['size'],
            'same_mtime' => $stat1['mtime'] === $stat2['mtime'],
            'same_mode' => ($stat1['mode'] & 0777) === ($stat2['mode'] & 0777),
            'same_owner' => $stat1['uid'] === $stat2['uid'],
            'same_group' => $stat1['gid'] === $stat2['gid'],
            'size_diff' => $stat1['size'] - $stat2['size'],
            'time_diff' => $stat1['mtime'] - $stat2['mtime'],
            'file1' => [
                'size' => $stat1['size'],
                'mtime' => $stat1['mtime'],
                'mode' => sprintf('%o', $stat1['mode'] & 0777)
            ],
            'file2' => [
                'size' => $stat2['size'],
                'mtime' => $stat2['mtime'],
                'mode' => sprintf('%o', $stat2['mode'] & 0777)
            ]
        ];
    }
    
    /**
     * ファイルが同一かチェック
     */
    public static function areIdentical($file1, $file2) {
        $comparison = self::compare($file1, $file2);
        
        if ($comparison === null) {
            return false;
        }
        
        // サイズと更新時刻が同じならば同一の可能性が高い
        if ($comparison['same_size'] && $comparison['same_mtime']) {
            // さらにハッシュで確認
            return md5_file($file1) === md5_file($file2);
        }
        
        return false;
    }
    
    /**
     * どちらのファイルが新しいか判定
     */
    public static function whichIsNewer($file1, $file2) {
        $stat1 = stat($file1);
        $stat2 = stat($file2);
        
        if ($stat1 === false || $stat2 === false) {
            return null;
        }
        
        if ($stat1['mtime'] > $stat2['mtime']) {
            return $file1;
        } elseif ($stat2['mtime'] > $stat1['mtime']) {
            return $file2;
        } else {
            return null;  // 同じ更新時刻
        }
    }
    
    /**
     * ファイルの年齢(最終更新からの経過時間)を比較
     */
    public static function compareAge($file1, $file2) {
        $stat1 = stat($file1);
        $stat2 = stat($file2);
        
        if ($stat1 === false || $stat2 === false) {
            return null;
        }
        
        $now = time();
        
        return [
            'file1_age' => $now - $stat1['mtime'],
            'file2_age' => $now - $stat2['mtime'],
            'age_diff' => abs($stat1['mtime'] - $stat2['mtime'])
        ];
    }
}

// 使用例
echo "=== ファイル比較 ===\n";

$file1 = '/tmp/file1.txt';
$file2 = '/tmp/file2.txt';

// 詳細比較
$comparison = FileComparator::compare($file1, $file2);
if ($comparison) {
    echo "サイズが同じ: " . ($comparison['same_size'] ? 'Yes' : 'No') . "\n";
    echo "更新時刻が同じ: " . ($comparison['same_mtime'] ? 'Yes' : 'No') . "\n";
    echo "パーミッションが同じ: " . ($comparison['same_mode'] ? 'Yes' : 'No') . "\n";
    echo "サイズ差: {$comparison['size_diff']} bytes\n";
}

// 同一性チェック
$identical = FileComparator::areIdentical($file1, $file2);
echo "\nファイルが同一: " . ($identical ? 'Yes' : 'No') . "\n";

// どちらが新しいか
$newer = FileComparator::whichIsNewer($file1, $file2);
if ($newer) {
    echo "新しいファイル: {$newer}\n";
} else {
    echo "両ファイルの更新時刻は同じ\n";
}

例4: パーミッションチェッカー

class PermissionChecker {
    /**
     * ファイルのパーミッションを取得
     */
    public static function getPermissions($file) {
        $stat = stat($file);
        
        if ($stat === false) {
            return null;
        }
        
        $mode = $stat['mode'];
        $perms = $mode & 0777;
        
        return [
            'octal' => sprintf('%o', $perms),
            'symbolic' => self::toSymbolic($mode),
            'owner_read' => (bool)($perms & 0400),
            'owner_write' => (bool)($perms & 0200),
            'owner_execute' => (bool)($perms & 0100),
            'group_read' => (bool)($perms & 040),
            'group_write' => (bool)($perms & 020),
            'group_execute' => (bool)($perms & 010),
            'other_read' => (bool)($perms & 04),
            'other_write' => (bool)($perms & 02),
            'other_execute' => (bool)($perms & 01)
        ];
    }
    
    /**
     * シンボリック形式に変換
     */
    private static function toSymbolic($mode) {
        $perms = '';
        
        // ファイルタイプ
        if (($mode & 0170000) === 0040000) $perms .= 'd';
        elseif (($mode & 0170000) === 0120000) $perms .= 'l';
        else $perms .= '-';
        
        // 所有者
        $perms .= ($mode & 0400) ? 'r' : '-';
        $perms .= ($mode & 0200) ? 'w' : '-';
        $perms .= ($mode & 0100) ? 'x' : '-';
        
        // グループ
        $perms .= ($mode & 040) ? 'r' : '-';
        $perms .= ($mode & 020) ? 'w' : '-';
        $perms .= ($mode & 010) ? 'x' : '-';
        
        // その他
        $perms .= ($mode & 04) ? 'r' : '-';
        $perms .= ($mode & 02) ? 'w' : '-';
        $perms .= ($mode & 01) ? 'x' : '-';
        
        return $perms;
    }
    
    /**
     * 読み取り可能かチェック
     */
    public static function isReadable($file) {
        return is_readable($file);
    }
    
    /**
     * 書き込み可能かチェック
     */
    public static function isWritable($file) {
        return is_writable($file);
    }
    
    /**
     * 実行可能かチェック
     */
    public static function isExecutable($file) {
        return is_executable($file);
    }
    
    /**
     * 安全なパーミッションかチェック
     */
    public static function isSafe($file) {
        $perms = self::getPermissions($file);
        
        if ($perms === null) {
            return false;
        }
        
        // ワールドライタブルでないことを確認
        if ($perms['other_write']) {
            return false;
        }
        
        return true;
    }
    
    /**
     * 推奨パーミッションと比較
     */
    public static function checkRecommended($file, $recommended = '0644') {
        $perms = self::getPermissions($file);
        
        if ($perms === null) {
            return null;
        }
        
        return [
            'current' => $perms['octal'],
            'recommended' => $recommended,
            'matches' => $perms['octal'] === $recommended
        ];
    }
}

// 使用例
echo "=== パーミッションチェック ===\n";

$file = '/tmp/test.txt';

// パーミッション取得
$perms = PermissionChecker::getPermissions($file);
if ($perms) {
    echo "8進数: {$perms['octal']}\n";
    echo "シンボリック: {$perms['symbolic']}\n";
    echo "所有者: " . ($perms['owner_read'] ? 'r' : '-') .
         ($perms['owner_write'] ? 'w' : '-') .
         ($perms['owner_execute'] ? 'x' : '-') . "\n";
}

// アクセス権チェック
echo "\n読み取り可能: " . (PermissionChecker::isReadable($file) ? 'Yes' : 'No') . "\n";
echo "書き込み可能: " . (PermissionChecker::isWritable($file) ? 'Yes' : 'No') . "\n";
echo "実行可能: " . (PermissionChecker::isExecutable($file) ? 'Yes' : 'No') . "\n";

// 安全性チェック
echo "\n安全なパーミッション: " . (PermissionChecker::isSafe($file) ? 'Yes' : 'No') . "\n";

// 推奨パーミッションと比較
$check = PermissionChecker::checkRecommended($file, '0644');
if ($check) {
    echo "\n現在: {$check['current']}\n";
    echo "推奨: {$check['recommended']}\n";
    echo "一致: " . ($check['matches'] ? 'Yes' : 'No') . "\n";
}

例5: ディスク使用量解析

class DiskUsageAnalyzer {
    /**
     * ディレクトリのサイズを計算
     */
    public static function getDirectorySize($directory) {
        if (!is_dir($directory)) {
            return 0;
        }
        
        $size = 0;
        $items = scandir($directory);
        
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            
            $path = $directory . DIRECTORY_SEPARATOR . $item;
            
            if (is_file($path)) {
                $stat = stat($path);
                if ($stat !== false) {
                    $size += $stat['size'];
                }
            } elseif (is_dir($path)) {
                $size += self::getDirectorySize($path);
            }
        }
        
        return $size;
    }
    
    /**
     * ファイル数を取得
     */
    public static function countFiles($directory) {
        if (!is_dir($directory)) {
            return 0;
        }
        
        $count = 0;
        $items = scandir($directory);
        
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            
            $path = $directory . DIRECTORY_SEPARATOR . $item;
            
            if (is_file($path)) {
                $count++;
            } elseif (is_dir($path)) {
                $count += self::countFiles($path);
            }
        }
        
        return $count;
    }
    
    /**
     * 最大ファイルサイズを取得
     */
    public static function getLargestFile($directory) {
        if (!is_dir($directory)) {
            return null;
        }
        
        $largest = null;
        $items = scandir($directory);
        
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            
            $path = $directory . DIRECTORY_SEPARATOR . $item;
            
            if (is_file($path)) {
                $stat = stat($path);
                if ($stat !== false) {
                    if ($largest === null || $stat['size'] > $largest['size']) {
                        $largest = [
                            'path' => $path,
                            'name' => $item,
                            'size' => $stat['size']
                        ];
                    }
                }
            } elseif (is_dir($path)) {
                $subLargest = self::getLargestFile($path);
                if ($subLargest && ($largest === null || $subLargest['size'] > $largest['size'])) {
                    $largest = $subLargest;
                }
            }
        }
        
        return $largest;
    }
    
    /**
     * 統計情報を取得
     */
    public static function getStats($directory) {
        return [
            'total_size' => self::getDirectorySize($directory),
            'file_count' => self::countFiles($directory),
            'largest_file' => self::getLargestFile($directory)
        ];
    }
    
    /**
     * サイズを人間が読みやすい形式に変換
     */
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }
}

// 使用例
echo "=== ディスク使用量解析 ===\n";

$directory = '/tmp';

$stats = DiskUsageAnalyzer::getStats($directory);

echo "総サイズ: " . DiskUsageAnalyzer::formatBytes($stats['total_size']) . "\n";
echo "ファイル数: {$stats['file_count']}\n";

if ($stats['largest_file']) {
    echo "\n最大ファイル:\n";
    echo "  名前: {$stats['largest_file']['name']}\n";
    echo "  サイズ: " . DiskUsageAnalyzer::formatBytes($stats['largest_file']['size']) . "\n";
}

例6: 変更検出システム

class FileChangeDetector {
    private $baseline = [];
    
    /**
     * ベースラインを作成
     */
    public function createBaseline($directory) {
        $this->baseline = [];
        $this->scanDirectory($directory);
    }
    
    /**
     * ディレクトリをスキャン
     */
    private function scanDirectory($directory) {
        if (!is_dir($directory)) {
            return;
        }
        
        $items = scandir($directory);
        
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            
            $path = $directory . DIRECTORY_SEPARATOR . $item;
            
            if (is_file($path)) {
                $stat = stat($path);
                if ($stat !== false) {
                    $this->baseline[$path] = [
                        'size' => $stat['size'],
                        'mtime' => $stat['mtime'],
                        'mode' => $stat['mode'],
                        'hash' => md5_file($path)
                    ];
                }
            } elseif (is_dir($path)) {
                $this->scanDirectory($path);
            }
        }
    }
    
    /**
     * 変更を検出
     */
    public function detectChanges($directory) {
        $changes = [
            'added' => [],
            'modified' => [],
            'deleted' => [],
            'unchanged' => []
        ];
        
        $current = [];
        $this->getCurrentState($directory, $current);
        
        // 追加されたファイル
        foreach ($current as $path => $info) {
            if (!isset($this->baseline[$path])) {
                $changes['added'][] = $path;
            }
        }
        
        // 削除されたファイル
        foreach ($this->baseline as $path => $info) {
            if (!isset($current[$path])) {
                $changes['deleted'][] = $path;
            }
        }
        
        // 変更されたファイル
        foreach ($current as $path => $info) {
            if (isset($this->baseline[$path])) {
                $baseline = $this->baseline[$path];
                
                if ($info['hash'] !== $baseline['hash']) {
                    $changes['modified'][] = [
                        'path' => $path,
                        'size_changed' => $info['size'] !== $baseline['size'],
                        'time_changed' => $info['mtime'] !== $baseline['mtime'],
                        'mode_changed' => $info['mode'] !== $baseline['mode']
                    ];
                } else {
                    $changes['unchanged'][] = $path;
                }
            }
        }
        
        return $changes;
    }
    
    /**
     * 現在の状態を取得
     */
    private function getCurrentState($directory, &$current) {
        if (!is_dir($directory)) {
            return;
        }
        
        $items = scandir($directory);
        
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            
            $path = $directory . DIRECTORY_SEPARATOR . $item;
            
            if (is_file($path)) {
                $stat = stat($path);
                if ($stat !== false) {
                    $current[$path] = [
                        'size' => $stat['size'],
                        'mtime' => $stat['mtime'],
                        'mode' => $stat['mode'],
                        'hash' => md5_file($path)
                    ];
                }
            } elseif (is_dir($path)) {
                $this->getCurrentState($path, $current);
            }
        }
    }
}

// 使用例
echo "=== 変更検出システム ===\n";

$detector = new FileChangeDetector();
$directory = '/tmp/watch';

// ベースライン作成
$detector->createBaseline($directory);
echo "ベースラインを作成しました\n";

// 少し待機
sleep(1);

// 変更を検出
$changes = $detector->detectChanges($directory);

echo "\n検出結果:\n";
echo "追加: " . count($changes['added']) . "ファイル\n";
echo "変更: " . count($changes['modified']) . "ファイル\n";
echo "削除: " . count($changes['deleted']) . "ファイル\n";
echo "未変更: " . count($changes['unchanged']) . "ファイル\n";

if (!empty($changes['modified'])) {
    echo "\n変更されたファイル:\n";
    foreach ($changes['modified'] as $mod) {
        echo "  {$mod['path']}\n";
        echo "    サイズ変更: " . ($mod['size_changed'] ? 'Yes' : 'No') . "\n";
        echo "    時刻変更: " . ($mod['time_changed'] ? 'Yes' : 'No') . "\n";
    }
}

lstat()との違い

// シンボリックリンクの場合の違い

// リンクを作成(例)
// symlink('/tmp/original.txt', '/tmp/link.txt');

// stat() - リンク先の情報を取得
$stat_info = stat('/tmp/link.txt');
echo "stat()のサイズ: {$stat_info['size']}\n";

// lstat() - リンク自体の情報を取得
$lstat_info = lstat('/tmp/link.txt');
echo "lstat()のサイズ: {$lstat_info['size']}\n";

// 通常のファイルでは同じ結果
$file = '/tmp/normal.txt';
var_dump(stat($file) === lstat($file));  // true(シンボリックリンクでない場合)

個別関数との関係

$file = 'test.txt';
$stat = stat($file);

// stat()で取得できる情報は個別関数でも取得可能
echo filesize($file);     // $stat['size']と同じ
echo filemtime($file);    // $stat['mtime']と同じ
echo fileatime($file);    // $stat['atime']と同じ
echo filectime($file);    // $stat['ctime']と同じ
echo fileperms($file);    // $stat['mode']と同じ
echo fileinode($file);    // $stat['ino']と同じ
echo fileowner($file);    // $stat['uid']と同じ
echo filegroup($file);    // $stat['gid']と同じ

// 複数の情報が必要な場合はstat()の方が効率的
// 1回のシステムコールですべての情報を取得

まとめ

stat()関数の特徴をまとめると:

できること:

  • ファイル/ディレクトリの詳細情報を取得
  • サイズ、タイムスタンプ、パーミッション等
  • 13種類の情報を一度に取得

戻り値:

  • 13要素の配列
  • 数値インデックスと文字列キーの両方
  • 失敗時はfalse

推奨される使用場面:

  • ファイル情報の解析
  • ファイル監視システム
  • バックアップシステム
  • パーミッション管理
  • ディスク使用量分析

主要な情報:

  • size: ファイルサイズ
  • mtime: 最終更新時刻
  • atime: 最終アクセス時刻
  • ctime: 最終変更時刻
  • mode: パーミッション
  • uid/gid: 所有者情報
  • ino: inode番号

関連関数:

  • lstat(): シンボリックリンク自体の情報
  • filesize(): サイズのみ取得
  • filemtime(): 更新時刻のみ取得
  • fileperms(): パーミッションのみ取得
  • file_exists(): 存在確認
  • is_file()/is_dir(): タイプ確認

よく使うパターン:

// ファイル情報を一度に取得
$info = stat($file);
$size = $info['size'];
$mtime = $info['mtime'];

// パーミッション確認
$mode = $info['mode'] & 0777;
$perms = sprintf('%o', $mode);

// 更新時刻の確認
$age = time() - $info['mtime'];

stat()は、ファイルシステムの詳細情報を取得する強力な関数です。ファイル監視、バックアップ、セキュリティチェックなど、様々な場面で活躍します!

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