はじめに
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関数は内部で以下のような処理を行っています:
- gzip圧縮されたファイルを開く
- ファイルの内容を読み込みながら解凍
- 解凍した内容を出力バッファに送る
- ファイルを閉じる
これを手動で書くと以下のようになりますが、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関数のポイントをおさらいしましょう:
- gzip圧縮されたファイルを自動的に解凍して出力する関数
- 圧縮ログファイルの表示に最適
- readfile()との使い分けが重要(圧縮ファイルのみ対象)
- セキュリティ対策は必須(パストラバーサル攻撃に注意)
- 大容量ファイルは出力バッファリングを無効化
- gzip形式の検証を行うとより安全
- エラーハンドリングを忘れずに
gzip圧縮されたログファイルは、サーバー容量の節約に効果的です。readgzfile関数を活用して、効率的にログファイルを管理・閲覧できるようにしましょう!
参考リンク
この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。
