[PHP]sha1_file関数を完全解説!ファイルのSHA-1ハッシュを計算する方法

PHP

こんにちは!今回は、PHPの標準関数であるsha1_file()について詳しく解説していきます。ファイル全体を読み込むことなく、直接SHA-1ハッシュ値を計算できる便利な関数です!

sha1_file関数とは?

sha1_file()関数は、ファイルのSHA-1ハッシュ値を計算する関数です。

大きなファイルでもメモリ効率よくハッシュ値を計算でき、ファイルの整合性確認、重複検出、バージョン管理などで活躍します!

基本的な構文

sha1_file(string $filename, bool $binary = false): string|false
  • $filename: ハッシュ化するファイルのパス
  • $binary: trueで生のバイナリ出力(20バイト)、falseで16進数文字列(40文字)
  • 戻り値: SHA-1ハッシュ値、失敗時はfalse

基本的な使用例

シンプルなファイルハッシュ

// テストファイルを作成
file_put_contents('/tmp/test.txt', 'Hello World');

// ファイルのハッシュを計算
$hash = sha1_file('/tmp/test.txt');
echo "ハッシュ: {$hash}\n";
// 出力: 0a4d55a8d778e5022fab701977c5d840bbc486d0

// 同じ内容なら同じハッシュ
file_put_contents('/tmp/test2.txt', 'Hello World');
$hash2 = sha1_file('/tmp/test2.txt');
echo "同一: " . ($hash === $hash2 ? 'Yes' : 'No') . "\n";
// 出力: Yes

バイナリ出力

// 16進数文字列(デフォルト)
$hash = sha1_file('/tmp/test.txt', false);
echo "16進数: {$hash}\n";
echo "長さ: " . strlen($hash) . "文字\n";
// 長さ: 40文字

// バイナリ出力
$hash = sha1_file('/tmp/test.txt', true);
echo "バイナリ長さ: " . strlen($hash) . "バイト\n";
echo "16進数変換: " . bin2hex($hash) . "\n";
// 長さ: 20バイト

エラーハンドリング

// 存在しないファイル
$hash = sha1_file('/nonexistent/file.txt');

if ($hash === false) {
    echo "エラー: ファイルが見つかりません\n";
}

// ファイル存在チェック
if (file_exists('/tmp/test.txt')) {
    $hash = sha1_file('/tmp/test.txt');
    echo "ハッシュ: {$hash}\n";
} else {
    echo "ファイルが存在しません\n";
}

大きなファイルの処理

// 大きなファイルを作成
$bigFile = '/tmp/bigfile.bin';
$fp = fopen($bigFile, 'w');
for ($i = 0; $i < 1000000; $i++) {
    fwrite($fp, str_repeat('A', 100));
}
fclose($fp);

echo "ファイルサイズ: " . filesize($bigFile) . " バイト\n";

// メモリ効率よくハッシュを計算
$start = microtime(true);
$hash = sha1_file($bigFile);
$time = microtime(true) - $start;

echo "ハッシュ: {$hash}\n";
echo "処理時間: {$time}秒\n";

実践的な使用例

例1: ファイル整合性チェッカー

class FileIntegrityChecker {
    private $checksumFile;
    
    public function __construct($checksumFile = 'checksums.json') {
        $this->checksumFile = $checksumFile;
    }
    
    /**
     * ファイルのチェックサムを計算
     */
    public function calculateChecksum($filepath) {
        if (!file_exists($filepath)) {
            return null;
        }
        
        return sha1_file($filepath);
    }
    
    /**
     * チェックサムを保存
     */
    public function saveChecksum($filepath, $checksum = null) {
        if ($checksum === null) {
            $checksum = $this->calculateChecksum($filepath);
            
            if ($checksum === null) {
                return false;
            }
        }
        
        $checksums = $this->loadChecksums();
        $checksums[$filepath] = [
            'checksum' => $checksum,
            'timestamp' => time(),
            'filesize' => filesize($filepath)
        ];
        
        return file_put_contents(
            $this->checksumFile,
            json_encode($checksums, JSON_PRETTY_PRINT)
        );
    }
    
    /**
     * 保存されたチェックサムを読み込み
     */
    private function loadChecksums() {
        if (!file_exists($this->checksumFile)) {
            return [];
        }
        
        $content = file_get_contents($this->checksumFile);
        return json_decode($content, true) ?? [];
    }
    
    /**
     * ファイルの整合性を検証
     */
    public function verify($filepath) {
        $checksums = $this->loadChecksums();
        
        if (!isset($checksums[$filepath])) {
            return [
                'status' => 'unknown',
                'message' => 'チェックサムが記録されていません'
            ];
        }
        
        $currentChecksum = $this->calculateChecksum($filepath);
        
        if ($currentChecksum === null) {
            return [
                'status' => 'error',
                'message' => 'ファイルが見つかりません'
            ];
        }
        
        $expected = $checksums[$filepath]['checksum'];
        
        if ($currentChecksum === $expected) {
            return [
                'status' => 'valid',
                'message' => 'ファイルは改変されていません',
                'checksum' => $currentChecksum
            ];
        }
        
        return [
            'status' => 'invalid',
            'message' => 'ファイルが改変されています',
            'expected' => $expected,
            'actual' => $currentChecksum
        ];
    }
    
    /**
     * 複数ファイルを一括検証
     */
    public function verifyAll($filepaths) {
        $results = [];
        
        foreach ($filepaths as $filepath) {
            $results[$filepath] = $this->verify($filepath);
        }
        
        return $results;
    }
    
    /**
     * ディレクトリ全体をスキャン
     */
    public function scanDirectory($directory, $save = true) {
        $results = [];
        $files = glob($directory . '/*');
        
        foreach ($files as $file) {
            if (is_file($file)) {
                $checksum = $this->calculateChecksum($file);
                $results[$file] = $checksum;
                
                if ($save) {
                    $this->saveChecksum($file, $checksum);
                }
            }
        }
        
        return $results;
    }
}

// 使用例
echo "=== ファイル整合性チェック ===\n";

// テストファイルを作成
file_put_contents('/tmp/document.txt', 'Original content');
file_put_contents('/tmp/image.jpg', str_repeat('X', 1000));

$checker = new FileIntegrityChecker('/tmp/checksums.json');

// チェックサムを保存
$checker->saveChecksum('/tmp/document.txt');
$checker->saveChecksum('/tmp/image.jpg');
echo "チェックサムを保存しました\n";

// 検証(正常)
$result = $checker->verify('/tmp/document.txt');
echo "\n検証結果: {$result['status']}\n";
echo "メッセージ: {$result['message']}\n";

// ファイルを改変
file_put_contents('/tmp/document.txt', 'Modified content');

// 再検証(改変検出)
$result = $checker->verify('/tmp/document.txt');
echo "\n検証結果: {$result['status']}\n";
echo "メッセージ: {$result['message']}\n";
if (isset($result['expected']) && isset($result['actual'])) {
    echo "期待値: {$result['expected']}\n";
    echo "実際値: {$result['actual']}\n";
}

// ディレクトリスキャン
echo "\n=== ディレクトリスキャン ===\n";
$checksums = $checker->scanDirectory('/tmp');
echo count($checksums) . "個のファイルをスキャンしました\n";

例2: 重複ファイル検出

class DuplicateFileFinder {
    /**
     * ディレクトリ内の重複ファイルを検出
     */
    public function findDuplicates($directory, $recursive = false) {
        $hashes = [];
        $files = $this->getFiles($directory, $recursive);
        
        foreach ($files as $file) {
            $hash = sha1_file($file);
            
            if ($hash === false) {
                continue;
            }
            
            if (!isset($hashes[$hash])) {
                $hashes[$hash] = [];
            }
            
            $hashes[$hash][] = [
                'path' => $file,
                'size' => filesize($file),
                'modified' => filemtime($file)
            ];
        }
        
        // 2つ以上のファイルがあるグループのみ返す
        return array_filter($hashes, function($files) {
            return count($files) > 1;
        });
    }
    
    /**
     * ファイルリストを取得
     */
    private function getFiles($directory, $recursive) {
        $files = [];
        
        if ($recursive) {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($directory)
            );
            
            foreach ($iterator as $file) {
                if ($file->isFile()) {
                    $files[] = $file->getPathname();
                }
            }
        } else {
            $items = glob($directory . '/*');
            
            foreach ($items as $item) {
                if (is_file($item)) {
                    $files[] = $item;
                }
            }
        }
        
        return $files;
    }
    
    /**
     * レポートを生成
     */
    public function generateReport($directory, $recursive = false) {
        $duplicates = $this->findDuplicates($directory, $recursive);
        
        $totalDuplicates = 0;
        $wastedSpace = 0;
        
        foreach ($duplicates as $hash => $files) {
            $fileCount = count($files);
            $fileSize = $files[0]['size'];
            
            $totalDuplicates += $fileCount - 1;
            $wastedSpace += $fileSize * ($fileCount - 1);
        }
        
        return [
            'duplicate_groups' => count($duplicates),
            'total_duplicates' => $totalDuplicates,
            'wasted_space' => $wastedSpace,
            'wasted_space_mb' => round($wastedSpace / 1024 / 1024, 2),
            'duplicates' => $duplicates
        ];
    }
    
    /**
     * 重複の1つを残して削除
     */
    public function removeDuplicates($directory, $keepOldest = true) {
        $duplicates = $this->findDuplicates($directory, false);
        $removed = [];
        
        foreach ($duplicates as $hash => $files) {
            // ソート
            usort($files, function($a, $b) use ($keepOldest) {
                return $keepOldest ? 
                    $a['modified'] <=> $b['modified'] :
                    $b['modified'] <=> $a['modified'];
            });
            
            // 最初の1つを残して削除
            for ($i = 1; $i < count($files); $i++) {
                if (unlink($files[$i]['path'])) {
                    $removed[] = $files[$i]['path'];
                }
            }
        }
        
        return $removed;
    }
}

// 使用例
echo "=== 重複ファイル検出 ===\n";

// テストファイルを作成
file_put_contents('/tmp/file1.txt', 'Content A');
file_put_contents('/tmp/file2.txt', 'Content A');  // 重複
file_put_contents('/tmp/file3.txt', 'Content B');
file_put_contents('/tmp/file4.txt', 'Content A');  // 重複

$finder = new DuplicateFileFinder();

// 重複を検出
$duplicates = $finder->findDuplicates('/tmp');

echo "重複グループ数: " . count($duplicates) . "\n\n";

foreach ($duplicates as $hash => $files) {
    echo "ハッシュ: {$hash}\n";
    echo "ファイル数: " . count($files) . "\n";
    
    foreach ($files as $file) {
        echo "  - {$file['path']} ({$file['size']} bytes)\n";
    }
    echo "\n";
}

// レポート生成
echo "=== レポート ===\n";
$report = $finder->generateReport('/tmp');
echo "重複グループ: {$report['duplicate_groups']}\n";
echo "重複ファイル数: {$report['total_duplicates']}\n";
echo "無駄な容量: {$report['wasted_space_mb']} MB\n";

例3: ファイルバージョン管理

class FileVersionManager {
    private $versionDir;
    
    public function __construct($versionDir = '/tmp/versions') {
        $this->versionDir = $versionDir;
        
        if (!is_dir($versionDir)) {
            mkdir($versionDir, 0755, true);
        }
    }
    
    /**
     * ファイルのバージョンを保存
     */
    public function saveVersion($filepath, $comment = '') {
        if (!file_exists($filepath)) {
            return false;
        }
        
        $hash = sha1_file($filepath);
        $filename = basename($filepath);
        $versionPath = $this->versionDir . '/' . $filename . '.' . $hash;
        
        // 既に同じバージョンが存在する場合はスキップ
        if (file_exists($versionPath)) {
            return [
                'status' => 'exists',
                'hash' => $hash,
                'message' => '同じバージョンが既に存在します'
            ];
        }
        
        // ファイルをコピー
        if (!copy($filepath, $versionPath)) {
            return false;
        }
        
        // メタデータを保存
        $metadataPath = $versionPath . '.meta';
        $metadata = [
            'original_path' => $filepath,
            'hash' => $hash,
            'timestamp' => time(),
            'filesize' => filesize($filepath),
            'comment' => $comment
        ];
        
        file_put_contents($metadataPath, json_encode($metadata, JSON_PRETTY_PRINT));
        
        return [
            'status' => 'saved',
            'hash' => $hash,
            'version_path' => $versionPath
        ];
    }
    
    /**
     * ファイルのバージョン履歴を取得
     */
    public function getVersions($filename) {
        $pattern = $this->versionDir . '/' . $filename . '.*';
        $files = glob($pattern);
        
        $versions = [];
        
        foreach ($files as $file) {
            if (substr($file, -5) === '.meta') {
                continue;
            }
            
            $metadataPath = $file . '.meta';
            
            if (file_exists($metadataPath)) {
                $metadata = json_decode(file_get_contents($metadataPath), true);
                $versions[] = $metadata;
            }
        }
        
        // タイムスタンプでソート
        usort($versions, function($a, $b) {
            return $b['timestamp'] <=> $a['timestamp'];
        });
        
        return $versions;
    }
    
    /**
     * 特定バージョンを復元
     */
    public function restore($filepath, $hash) {
        $filename = basename($filepath);
        $versionPath = $this->versionDir . '/' . $filename . '.' . $hash;
        
        if (!file_exists($versionPath)) {
            return false;
        }
        
        return copy($versionPath, $filepath);
    }
    
    /**
     * 現在のファイルと比較
     */
    public function compareWithCurrent($filepath) {
        if (!file_exists($filepath)) {
            return null;
        }
        
        $currentHash = sha1_file($filepath);
        $versions = $this->getVersions(basename($filepath));
        
        $hasChanges = true;
        
        foreach ($versions as $version) {
            if ($version['hash'] === $currentHash) {
                $hasChanges = false;
                break;
            }
        }
        
        return [
            'current_hash' => $currentHash,
            'has_changes' => $hasChanges,
            'version_count' => count($versions)
        ];
    }
}

// 使用例
echo "=== ファイルバージョン管理 ===\n";

$manager = new FileVersionManager();
$testFile = '/tmp/document.txt';

// バージョン1
file_put_contents($testFile, 'Version 1 content');
$result = $manager->saveVersion($testFile, 'Initial version');
echo "保存: {$result['status']} (ハッシュ: {$result['hash']})\n";

// バージョン2
file_put_contents($testFile, 'Version 2 content - updated');
$result = $manager->saveVersion($testFile, 'Added more content');
echo "保存: {$result['status']} (ハッシュ: {$result['hash']})\n";

// バージョン3
file_put_contents($testFile, 'Version 3 content - final');
$result = $manager->saveVersion($testFile, 'Final version');
echo "保存: {$result['status']} (ハッシュ: {$result['hash']})\n";

// バージョン履歴を表示
echo "\n=== バージョン履歴 ===\n";
$versions = $manager->getVersions('document.txt');

foreach ($versions as $i => $version) {
    echo "バージョン " . ($i + 1) . ":\n";
    echo "  ハッシュ: {$version['hash']}\n";
    echo "  日時: " . date('Y-m-d H:i:s', $version['timestamp']) . "\n";
    echo "  サイズ: {$version['filesize']} bytes\n";
    echo "  コメント: {$version['comment']}\n\n";
}

// 現在との比較
$comparison = $manager->compareWithCurrent($testFile);
echo "=== 現在のファイル ===\n";
echo "ハッシュ: {$comparison['current_hash']}\n";
echo "未保存の変更: " . ($comparison['has_changes'] ? 'あり' : 'なし') . "\n";

例4: ダウンロード検証システム

class DownloadVerifier {
    /**
     * ダウンロード用のメタデータを生成
     */
    public static function generateMetadata($filepath) {
        if (!file_exists($filepath)) {
            return null;
        }
        
        return [
            'filename' => basename($filepath),
            'filesize' => filesize($filepath),
            'sha1' => sha1_file($filepath),
            'md5' => md5_file($filepath),
            'created' => time()
        ];
    }
    
    /**
     * チェックサムファイルを生成
     */
    public static function createChecksumFile($filepath) {
        $metadata = self::generateMetadata($filepath);
        
        if ($metadata === null) {
            return false;
        }
        
        $checksumFile = $filepath . '.sha1';
        $content = "{$metadata['sha1']}  {$metadata['filename']}\n";
        
        return file_put_contents($checksumFile, $content);
    }
    
    /**
     * ダウンロードしたファイルを検証
     */
    public static function verifyDownload($filepath, $expectedHash) {
        if (!file_exists($filepath)) {
            return [
                'valid' => false,
                'error' => 'ファイルが見つかりません'
            ];
        }
        
        $actualHash = sha1_file($filepath);
        
        return [
            'valid' => $actualHash === $expectedHash,
            'expected' => $expectedHash,
            'actual' => $actualHash,
            'filesize' => filesize($filepath)
        ];
    }
    
    /**
     * チェックサムファイルを読み込んで検証
     */
    public static function verifyWithChecksumFile($filepath) {
        $checksumFile = $filepath . '.sha1';
        
        if (!file_exists($checksumFile)) {
            return [
                'valid' => false,
                'error' => 'チェックサムファイルが見つかりません'
            ];
        }
        
        $content = file_get_contents($checksumFile);
        $parts = preg_split('/\s+/', trim($content));
        
        if (count($parts) < 1) {
            return [
                'valid' => false,
                'error' => 'チェックサムファイルの形式が不正です'
            ];
        }
        
        $expectedHash = $parts[0];
        
        return self::verifyDownload($filepath, $expectedHash);
    }
    
    /**
     * 進行状況付きでファイルを検証
     */
    public static function verifyWithProgress($filepath, $expectedHash, $callback = null) {
        if (!file_exists($filepath)) {
            return false;
        }
        
        $filesize = filesize($filepath);
        $chunkSize = 1024 * 1024; // 1MB
        $hash = hash_init('sha1');
        $fp = fopen($filepath, 'rb');
        $processed = 0;
        
        while (!feof($fp)) {
            $chunk = fread($fp, $chunkSize);
            hash_update($hash, $chunk);
            $processed += strlen($chunk);
            
            if ($callback !== null) {
                $progress = ($processed / $filesize) * 100;
                call_user_func($callback, $progress, $processed, $filesize);
            }
        }
        
        fclose($fp);
        $actualHash = hash_final($hash);
        
        return $actualHash === $expectedHash;
    }
}

// 使用例
echo "=== ダウンロード検証 ===\n";

// テストファイルを作成
$testFile = '/tmp/download.zip';
file_put_contents($testFile, str_repeat('DATA', 10000));

// メタデータ生成
$metadata = DownloadVerifier::generateMetadata($testFile);
echo "ファイル: {$metadata['filename']}\n";
echo "サイズ: {$metadata['filesize']} bytes\n";
echo "SHA-1: {$metadata['sha1']}\n";

// チェックサムファイル生成
DownloadVerifier::createChecksumFile($testFile);
echo "\nチェックサムファイルを生成しました\n";

// 検証
$result = DownloadVerifier::verifyWithChecksumFile($testFile);
echo "\n検証結果: " . ($result['valid'] ? 'OK' : 'NG') . "\n";

// ファイルを改変
file_put_contents($testFile, 'CORRUPTED');

// 再検証
$result = DownloadVerifier::verifyWithChecksumFile($testFile);
echo "改変後の検証: " . ($result['valid'] ? 'OK' : 'NG') . "\n";
if (!$result['valid']) {
    echo "  期待値: {$result['expected']}\n";
    echo "  実際値: {$result['actual']}\n";
}

// 進行状況付き検証
echo "\n=== 進行状況付き検証 ===\n";
$testFile = '/tmp/largefile.bin';
file_put_contents($testFile, str_repeat('X', 10000000)); // 10MB

$expectedHash = sha1_file($testFile);

$isValid = DownloadVerifier::verifyWithProgress(
    $testFile,
    $expectedHash,
    function($progress, $processed, $total) {
        echo sprintf("\r検証中: %.1f%% (%d / %d bytes)", $progress, $processed, $total);
    }
);

echo "\n検証完了: " . ($isValid ? 'OK' : 'NG') . "\n";

例5: ファイル同期システム

class FileSynchronizer {
    /**
     * ファイルリストとハッシュを取得
     */
    public function getFileList($directory) {
        $files = [];
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory)
        );
        
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $relativePath = substr($file->getPathname(), strlen($directory) + 1);
                
                $files[$relativePath] = [
                    'path' => $file->getPathname(),
                    'hash' => sha1_file($file->getPathname()),
                    'size' => $file->getSize(),
                    'modified' => $file->getMTime()
                ];
            }
        }
        
        return $files;
    }
    
    /**
     * 2つのディレクトリを比較
     */
    public function compare($sourceDir, $targetDir) {
        $sourceFiles = $this->getFileList($sourceDir);
        $targetFiles = $this->getFileList($targetDir);
        
        $result = [
            'new' => [],        // ソースにのみ存在
            'modified' => [],   // 両方に存在するが変更あり
            'deleted' => [],    // ターゲットにのみ存在
            'unchanged' => []   // 変更なし
        ];
        
        // ソースのファイルをチェック
        foreach ($sourceFiles as $path => $sourceFile) {
            if (!isset($targetFiles[$path])) {
                $result['new'][] = $path;
            } elseif ($sourceFile['hash'] !== $targetFiles[$path]['hash']) {
                $result['modified'][] = $path;
            } else {
                $result['unchanged'][] = $path;
            }
        }
        
        // 削除されたファイルをチェック
        foreach ($targetFiles as $path => $targetFile) {
            if (!isset($sourceFiles[$path])) {
                $result['deleted'][] = $path;
            }
        }
        
        return $result;
    }
    
    /**
     * ファイルを同期
     */
    public function sync($sourceDir, $targetDir, $deleteExtra = false) {
        $comparison = $this->compare($sourceDir, $targetDir);
        $synced = [];
        
        // 新規ファイルをコピー
        foreach ($comparison['new'] as $path) {
            $sourcePath = $sourceDir . '/' . $path;
            $targetPath = $targetDir . '/' . $path;
            
            $targetDirPath = dirname($targetPath);
            if (!is_dir($targetDirPath)) {
                mkdir($targetDirPath, 0755, true);
            }
            
            if (copy($sourcePath, $targetPath)) {
                $synced['new'][] = $path;
            }
        }
        
        // 変更されたファイルをコピー
        foreach ($comparison['modified'] as $path) {
            $sourcePath = $sourceDir . '/' . $path;
            $targetPath = $targetDir . '/' . $path;
            
            if (copy($sourcePath, $targetPath)) {
                $synced['modified'][] = $path;
            }
        }
        
        // 余分なファイルを削除(オプション)
        if ($deleteExtra) {
            foreach ($comparison['deleted'] as $path) {
                $targetPath = $targetDir . '/' . $path;
                
                if (unlink($targetPath)) {
                    $synced['deleted'][] = $path;
                }
            }
        }
        
        return $synced;
    }
}

// 使用例
echo "=== ファイル同期 ===\n";

// ソースディレクトリとターゲットディレクトリを作成
$sourceDir = '/tmp/source';
$targetDir = '/tmp/target';

mkdir($sourceDir, 0755, true);
mkdir($targetDir, 0755, true);

// ソースにファイルを作成
file_put_contents($sourceDir . '/file1.txt', 'Content 1');
file_put_contents($sourceDir . '/file2.txt', 'Content 2');
mkdir($sourceDir . '/subdir', 0755, true);
file_put_contents($sourceDir . '/subdir/file3.txt', 'Content 3');

// ターゲットに古いバージョンを作成
file_put_contents($targetDir . '/file2.txt', 'Old Content 2');
file_put_contents($targetDir . '/file4.txt', 'Extra file');

$synchronizer = new FileSynchronizer();

// 比較
$comparison = $synchronizer->compare($sourceDir, $targetDir);
echo "新規: " . count($comparison['new']) . "件\n";
echo "変更: " . count($comparison['modified']) . "件\n";
echo "削除: " . count($comparison['deleted']) . "件\n";
echo "変更なし: " . count($comparison['unchanged']) . "件\n";

// 同期
echo "\n=== 同期実行 ===\n";
$synced = $synchronizer->sync($sourceDir, $targetDir, true);
print_r($synced);

例6: バックアップシステム

class BackupManager {
    private $backupDir;
    
    public function __construct($backupDir) {
        $this->backupDir = $backupDir;
        
        if (!is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }
    }
    
    /**
     * 増分バックアップを作成
     */
    public function incrementalBackup($sourceDir, $backupName) {
        $backupPath = $this->backupDir . '/' . $backupName;
        $manifestPath = $backupPath . '/manifest.json';
        
        // 既存のマニフェストを読み込み
        $previousManifest = [];
        if (file_exists($manifestPath)) {
            $previousManifest = json_decode(file_get_contents($manifestPath), true);
        }
        
        // 現在のファイルリストを取得
        $currentFiles = $this->scanDirectory($sourceDir);
        $backed = [];
        $skipped = [];
        
        foreach ($currentFiles as $relativePath => $fileInfo) {
            $targetPath = $backupPath . '/' . $relativePath;
            
            // 前回のバックアップと比較
            if (isset($previousManifest[$relativePath]) &&
                $previousManifest[$relativePath]['hash'] === $fileInfo['hash']) {
                $skipped[] = $relativePath;
                continue;
            }
            
            // バックアップ
            $targetDir = dirname($targetPath);
            if (!is_dir($targetDir)) {
                mkdir($targetDir, 0755, true);
            }
            
            if (copy($fileInfo['path'], $targetPath)) {
                $backed[] = $relativePath;
            }
        }
        
        // マニフェストを更新
        file_put_contents(
            $manifestPath,
            json_encode($currentFiles, JSON_PRETTY_PRINT)
        );
        
        return [
            'backed_up' => count($backed),
            'skipped' => count($skipped),
            'files' => $backed
        ];
    }
    
    /**
     * ディレクトリをスキャン
     */
    private function scanDirectory($directory) {
        $files = [];
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($directory)
        );
        
        foreach ($iterator as $file) {
            if ($file->isFile()) {
                $relativePath = substr($file->getPathname(), strlen($directory) + 1);
                
                $files[$relativePath] = [
                    'path' => $file->getPathname(),
                    'hash' => sha1_file($file->getPathname()),
                    'size' => $file->getSize(),
                    'modified' => $file->getMTime()
                ];
            }
        }
        
        return $files;
    }
    
    /**
     * バックアップを復元
     */
    public function restore($backupName, $targetDir) {
        $backupPath = $this->backupDir . '/' . $backupName;
        $manifestPath = $backupPath . '/manifest.json';
        
        if (!file_exists($manifestPath)) {
            return false;
        }
        
        $manifest = json_decode(file_get_contents($manifestPath), true);
        $restored = [];
        
        foreach ($manifest as $relativePath => $fileInfo) {
            $sourcePath = $backupPath . '/' . $relativePath;
            $targetPath = $targetDir . '/' . $relativePath;
            
            if (!file_exists($sourcePath)) {
                continue;
            }
            
            $targetDirPath = dirname($targetPath);
            if (!is_dir($targetDirPath)) {
                mkdir($targetDirPath, 0755, true);
            }
            
            if (copy($sourcePath, $targetPath)) {
                $restored[] = $relativePath;
            }
        }
        
        return $restored;
    }
}

// 使用例
echo "=== バックアップシステム ===\n";

$backupManager = new BackupManager('/tmp/backups');
$sourceDir = '/tmp/mydata';

// ソースディレクトリを作成
mkdir($sourceDir, 0755, true);
file_put_contents($sourceDir . '/document1.txt', 'Document 1');
file_put_contents($sourceDir . '/document2.txt', 'Document 2');

// 初回バックアップ
$result = $backupManager->incrementalBackup($sourceDir, 'backup1');
echo "バックアップ完了:\n";
echo "  バックアップ: {$result['backed_up']}件\n";
echo "  スキップ: {$result['skipped']}件\n";

// ファイルを変更
file_put_contents($sourceDir . '/document1.txt', 'Document 1 - Updated');
file_put_contents($sourceDir . '/document3.txt', 'Document 3 - New');

// 増分バックアップ
echo "\n=== 増分バックアップ ===\n";
$result = $backupManager->incrementalBackup($sourceDir, 'backup1');
echo "バックアップ完了:\n";
echo "  バックアップ: {$result['backed_up']}件\n";
echo "  スキップ: {$result['skipped']}件\n";

md5_file()との比較

$testFile = '/tmp/test.txt';
file_put_contents($testFile, 'Test content');

// SHA-1(160ビット、40文字)
$sha1 = sha1_file($testFile);
echo "SHA-1: {$sha1}\n";
echo "長さ: " . strlen($sha1) . "文字\n";

// MD5(128ビット、32文字)
$md5 = md5_file($testFile);
echo "MD5: {$md5}\n";
echo "長さ: " . strlen($md5) . "文字\n";

// SHA-256(より安全)
$sha256 = hash_file('sha256', $testFile);
echo "SHA-256: {$sha256}\n";
echo "長さ: " . strlen($sha256) . "文字\n";

まとめ

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

できること:

  • ファイルの SHA-1ハッシュを計算
  • メモリ効率の良い処理
  • 大きなファイルにも対応

推奨される使用場面:

  • ファイル整合性チェック
  • 重複ファイル検出
  • バージョン管理
  • ダウンロード検証
  • ファイル同期
  • バックアップシステム

利点:

  • メモリを大量に使わない
  • 大きなファイルでも高速
  • MD5より衝突耐性が高い

注意点:

  • セキュリティ用途には不適切(SHA-256以上推奨)
  • ファイルが存在しない場合はfalseを返す
  • 読み取り権限が必要

関連関数:

  • sha1(): 文字列のSHA-1ハッシュ
  • md5_file(): ファイルのMD5ハッシュ
  • hash_file(): 様々なハッシュアルゴリズム

使い分け:

// ファイル整合性、重複検出: SHA-1でOK
$hash = sha1_file($filepath);

// セキュリティ重要: SHA-256以上
$hash = hash_file('sha256', $filepath);

// 高速チェック: MD5(セキュリティ不要な場合)
$hash = md5_file($filepath);

sha1_file()は、ファイルの整合性確認や重複検出に非常に便利です。大きなファイルでもメモリ効率よく処理できるので、ファイル管理システムで活躍します!

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