[PHP]readgzfile関数の使い方を徹底解説!gzip圧縮ファイルの扱い方

PHP

はじめに

Webアプリケーションでは、ログファイルやバックアップデータなど、大容量のファイルを扱うことがよくあります。こうしたファイルは、サーバーのディスク容量を節約するためにgzip形式で圧縮されていることが多いですよね。

そんなgzip圧縮されたファイルを簡単に読み込んで出力できるのが、PHPのreadgzfile関数です。この記事では、readgzfile関数の基本から実践的な使い方まで、分かりやすく解説していきます。

readgzfile関数とは?

readgzfile関数は、gzip圧縮されたファイルを読み込んで、解凍しながら出力する関数です。圧縮ファイルを自動的に解凍してくれるため、手動で解凍処理を書く必要がありません。

基本的な構文

<?php
readgzfile(string $filename, int $use_include_path = 0): int|false
?>
  • $filename: 読み込むgzipファイルのパス
  • $use_include_path: include_pathからファイルを検索するか(オプション)
  • 戻り値: 読み込んだ解凍後のバイト数、失敗時はfalse

最もシンプルな使用例

<?php
// logs.txt.gzを解凍して内容を出力
readgzfile('logs.txt.gz');
?>

たったこれだけで、圧縮されたファイルが自動的に解凍されて出力されます!

readgzfile関数の仕組み

readgzfile関数は内部で以下のような処理を行っています:

  1. gzip圧縮されたファイルを開く
  2. ファイルの内容を読み込みながら解凍
  3. 解凍した内容を出力バッファに送る
  4. ファイルを閉じる

これを手動で書くと以下のようになりますが、readgzfileなら1行で済みます:

<?php
// readgzfile()と同等の処理を手動で行う場合
$gz = gzopen('logs.txt.gz', 'rb');
while (!gzeof($gz)) {
    echo gzread($gz, 8192);
}
gzclose($gz);

// readgzfile()ならこれだけ!
readgzfile('logs.txt.gz');
?>

readfile関数との違い

readgzfile関数とreadfile関数の違いを理解しておきましょう:

関数対象ファイル処理内容
readfile()通常のファイルそのまま読み込んで出力
readgzfile()gzip圧縮ファイル解凍しながら読み込んで出力
<?php
// 通常のファイル
readfile('data.txt');  // data.txtの内容をそのまま出力

// gzip圧縮ファイル
readgzfile('data.txt.gz');  // data.txt.gzを解凍して出力
?>

重要: readgzfile関数は.gz拡張子のファイル専用です。通常のファイルには使えません!

実践的な使用例

1. gzip圧縮されたログファイルの表示

<?php
$logFile = 'logs/access.log.gz';

if (file_exists($logFile)) {
    header('Content-Type: text/plain; charset=UTF-8');
    readgzfile($logFile);
} else {
    echo 'ログファイルが見つかりません';
}
?>

2. gzip圧縮ファイルのダウンロード(解凍して)

<?php
$gzFile = 'backups/data.sql.gz';
$downloadName = 'data.sql';

if (file_exists($gzFile)) {
    // 解凍したファイルとしてダウンロード
    header('Content-Type: text/plain; charset=UTF-8');
    header('Content-Disposition: attachment; filename="' . $downloadName . '"');
    
    readgzfile($gzFile);
    exit;
}
?>

3. gzip圧縮ファイルをそのままダウンロード(解凍しない)

<?php
$gzFile = 'backups/data.sql.gz';

if (file_exists($gzFile)) {
    // 圧縮されたままダウンロード
    header('Content-Type: application/gzip');
    header('Content-Disposition: attachment; filename="' . basename($gzFile) . '"');
    header('Content-Length: ' . filesize($gzFile));
    
    // readfile()を使う(readgzfileではない!)
    readfile($gzFile);
    exit;
}
?>

ポイント: 圧縮ファイルをそのままダウンロードさせたい場合はreadfile()を使い、解凍してからダウンロードさせたい場合はreadgzfile()を使います!

4. gzip圧縮されたCSVファイルの処理

<?php
$gzFile = 'data/users.csv.gz';

if (file_exists($gzFile)) {
    header('Content-Type: text/csv; charset=UTF-8');
    header('Content-Disposition: attachment; filename="users.csv"');
    
    // CSV BOMを追加(Excelで開く場合)
    echo "\xEF\xBB\xBF";
    
    readgzfile($gzFile);
    exit;
}
?>

5. 複数のgzip圧縮ログを結合して表示

<?php
$logFiles = [
    'logs/access.log.1.gz',
    'logs/access.log.2.gz',
    'logs/access.log.3.gz'
];

header('Content-Type: text/plain; charset=UTF-8');

foreach ($logFiles as $logFile) {
    if (file_exists($logFile)) {
        echo "=== {$logFile} ===\n";
        readgzfile($logFile);
        echo "\n\n";
    }
}
?>

6. gzip圧縮ファイルのサイズ情報を表示

<?php
$gzFile = 'data/large-file.txt.gz';

if (file_exists($gzFile)) {
    $compressedSize = filesize($gzFile);
    
    // 一時的にバッファリングして解凍後のサイズを取得
    ob_start();
    readgzfile($gzFile);
    $decompressed = ob_get_clean();
    $decompressedSize = strlen($decompressed);
    
    $ratio = round((1 - $compressedSize / $decompressedSize) * 100, 2);
    
    echo "圧縮ファイルサイズ: " . number_format($compressedSize) . " バイト\n";
    echo "解凍後サイズ: " . number_format($decompressedSize) . " バイト\n";
    echo "圧縮率: {$ratio}%\n\n";
    echo "=== ファイル内容 ===\n";
    echo $decompressed;
}
?>

セキュリティ上の重要な注意点

readgzfile関数を使う際も、readfile関数と同様にセキュリティに注意が必要です!

❌ 危険な例:パストラバーサル攻撃に脆弱

<?php
// 絶対にこのようなコードを書かないでください!
$file = $_GET['file'];
readgzfile($file . '.gz');
// 攻撃者が ?file=../../../../etc/passwd のようなリクエストを送ると危険
?>

✅ 安全な実装例

<?php
// ホワイトリスト方式
$allowedLogs = [
    'access' => 'logs/access.log.gz',
    'error' => 'logs/error.log.gz',
    'debug' => 'logs/debug.log.gz'
];

$logType = $_GET['log'] ?? '';

if (isset($allowedLogs[$logType])) {
    $logFile = $allowedLogs[$logType];
    
    if (file_exists($logFile)) {
        header('Content-Type: text/plain; charset=UTF-8');
        header('Content-Disposition: attachment; filename="' . basename($logFile, '.gz') . '"');
        
        readgzfile($logFile);
        exit;
    }
}

http_response_code(404);
echo 'ログファイルが見つかりません';
?>

パス検証の実装

<?php
function isGzFileSafe($baseDir, $requestedFile) {
    $realBase = realpath($baseDir);
    $realFile = realpath($requestedFile);
    
    if ($realFile === false) {
        return false;
    }
    
    // ベースディレクトリ配下にあるかチェック
    if (strpos($realFile, $realBase) !== 0) {
        return false;
    }
    
    // .gz拡張子かチェック
    if (pathinfo($realFile, PATHINFO_EXTENSION) !== 'gz') {
        return false;
    }
    
    return true;
}

$baseDir = '/var/log/app';
$filename = $_GET['file'] ?? '';
$fullPath = $baseDir . '/' . $filename;

if (isGzFileSafe($baseDir, $fullPath) && file_exists($fullPath)) {
    header('Content-Type: text/plain; charset=UTF-8');
    readgzfile($fullPath);
    exit;
} else {
    http_response_code(403);
    echo 'アクセスが拒否されました';
}
?>

エラーハンドリング

readgzfile関数が失敗した場合の適切な処理:

<?php
$gzFile = 'data/important.txt.gz';

// ファイル存在チェック
if (!file_exists($gzFile)) {
    http_response_code(404);
    error_log("gzファイルが存在しません: {$gzFile}");
    die('ファイルが見つかりません');
}

// 読み取り権限チェック
if (!is_readable($gzFile)) {
    http_response_code(403);
    error_log("gzファイルの読み取り権限がありません: {$gzFile}");
    die('ファイルを読み取れません');
}

// gzip形式チェック(簡易版)
$handle = fopen($gzFile, 'rb');
$header = fread($handle, 2);
fclose($handle);

if ($header !== "\x1f\x8b") {  // gzipのマジックナンバー
    http_response_code(400);
    error_log("無効なgzipファイル: {$gzFile}");
    die('有効なgzipファイルではありません');
}

// ファイル読み込み
header('Content-Type: text/plain; charset=UTF-8');
$bytes = readgzfile($gzFile);

if ($bytes === false) {
    error_log("readgzfileエラー: {$gzFile}");
    die('ファイルの読み込みに失敗しました');
}

exit;
?>

パフォーマンスと大容量ファイル対策

出力バッファリングの無効化

大きなgzipファイルを扱う場合は、出力バッファリングを無効化すると良いでしょう:

<?php
$largeGzFile = 'logs/huge-log.gz';

// 出力バッファリングを無効化
if (ob_get_level()) {
    ob_end_clean();
}

header('Content-Type: text/plain; charset=UTF-8');
readgzfile($largeGzFile);
exit;
?>

進捗表示が必要な場合

非常に大きなファイルで進捗を表示したい場合は、gzopen/gzreadを使います:

<?php
function readGzFileWithProgress($gzFile) {
    $gz = gzopen($gzFile, 'rb');
    if (!$gz) {
        return false;
    }
    
    $totalBytes = 0;
    $chunkSize = 8192;  // 8KB
    
    while (!gzeof($gz)) {
        $chunk = gzread($gz, $chunkSize);
        echo $chunk;
        
        $totalBytes += strlen($chunk);
        
        // 進捗をログに記録(実際の出力には含めない)
        if ($totalBytes % (1024 * 1024) === 0) {  // 1MBごと
            error_log("進捗: " . ($totalBytes / 1024 / 1024) . "MB読み込み完了");
        }
        
        flush();  // バッファをフラッシュ
    }
    
    gzclose($gz);
    return $totalBytes;
}

header('Content-Type: text/plain; charset=UTF-8');
readGzFileWithProgress('logs/very-large.log.gz');
exit;
?>

gzip圧縮関連関数との比較

関数用途出力先
readgzfile()gzipファイル全体を読んで出力出力バッファ
gzfile()gzipファイルを配列として読み込み変数(配列)
gzopen() + gzread()gzipファイルを部分的に読み込み変数
gzgetss()gzipファイルから1行読み込み変数

使い分けの例

<?php
// gzipファイルをそのまま出力する場合
readgzfile('data.txt.gz');

// gzipファイルを配列として取得する場合
$lines = gzfile('data.txt.gz');
foreach ($lines as $line) {
    echo htmlspecialchars($line);
}

// gzipファイルを加工しながら読む場合
$gz = gzopen('data.txt.gz', 'rb');
while (!gzeof($gz)) {
    $line = gzgets($gz);
    $modified = str_replace('old', 'new', $line);
    echo $modified;
}
gzclose($gz);
?>

gzipファイルの作成方法

readgzfileで読み込むgzipファイルは、PHPで簡単に作成できます:

<?php
// 通常のファイルをgzip圧縮して保存
$data = "大量のログデータ...\n" . str_repeat("ログ行\n", 10000);
$gzFile = 'logs/output.log.gz';

// 方法1: gzwrite()を使う
$gz = gzopen($gzFile, 'wb9');  // 9は最高圧縮率
gzwrite($gz, $data);
gzclose($gz);

// 方法2: file_put_contents() + gzencode()を使う
file_put_contents($gzFile, gzencode($data, 9));

echo "gzipファイルを作成しました\n";
echo "圧縮前: " . strlen($data) . " バイト\n";
echo "圧縮後: " . filesize($gzFile) . " バイト\n";
?>

実用的な完全版スクリプト

最後に、実務で使える完全版のgzipログビューアーをご紹介します:

<?php
/**
 * gzip圧縮ログファイルビューアー
 */

// 設定
$logDir = '/var/log/myapp';
$allowedLogs = ['access', 'error', 'debug', 'api'];

// パラメータ取得
$logType = $_GET['type'] ?? '';
$action = $_GET['action'] ?? 'view';  // view or download

// ログタイプの検証
if (!in_array($logType, $allowedLogs)) {
    http_response_code(400);
    die('無効なログタイプです');
}

$gzFile = $logDir . '/' . $logType . '.log.gz';

// パス検証
$realLogDir = realpath($logDir);
$realGzFile = realpath($gzFile);

if ($realGzFile === false || strpos($realGzFile, $realLogDir) !== 0) {
    http_response_code(403);
    die('不正なアクセスです');
}

// ファイル存在確認
if (!file_exists($gzFile)) {
    http_response_code(404);
    die('ログファイルが見つかりません');
}

// 読み取り権限確認
if (!is_readable($gzFile)) {
    http_response_code(403);
    error_log("gzファイル読み取り不可: {$gzFile}");
    die('ログファイルを読み取れません');
}

// gzip形式確認
$handle = fopen($gzFile, 'rb');
$header = fread($handle, 2);
fclose($handle);

if ($header !== "\x1f\x8b") {
    http_response_code(500);
    error_log("無効なgzipファイル: {$gzFile}");
    die('ログファイルが破損しています');
}

// 出力バッファクリア
if (ob_get_level()) {
    ob_end_clean();
}

// ヘッダー設定
header('Content-Type: text/plain; charset=UTF-8');

if ($action === 'download') {
    $downloadName = $logType . '_' . date('Ymd_His') . '.log';
    header('Content-Disposition: attachment; filename="' . $downloadName . '"');
} else {
    header('Content-Disposition: inline');
}

// キャッシュ無効化
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');

// ファイル読み込み
$bytes = readgzfile($gzFile);

if ($bytes === false) {
    error_log("readgzfileエラー: {$gzFile}");
    die('ログファイルの読み込みに失敗しました');
}

// アクセスログを記録
error_log("ログアクセス: {$logType} by " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));

exit;
?>

まとめ

readgzfile関数のポイントをおさらいしましょう:

  1. gzip圧縮されたファイルを自動的に解凍して出力する関数
  2. 圧縮ログファイルの表示に最適
  3. readfile()との使い分けが重要(圧縮ファイルのみ対象)
  4. セキュリティ対策は必須(パストラバーサル攻撃に注意)
  5. 大容量ファイルは出力バッファリングを無効化
  6. gzip形式の検証を行うとより安全
  7. エラーハンドリングを忘れずに

gzip圧縮されたログファイルは、サーバー容量の節約に効果的です。readgzfile関数を活用して、効率的にログファイルを管理・閲覧できるようにしましょう!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。

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