Webサイトの表示速度を改善したい、転送量を削減したいと考えたことはありませんか?PHPには、出力を自動的に圧縮してくれる便利な機能があります。それがob_gzhandler関数です。
この記事では、ob_gzhandler関数の基本的な使い方から、実践的なパフォーマンス最適化まで詳しく解説します。
ob_gzhandler関数とは?
ob_gzhandler()
は、PHPの出力バッファリング用のハンドラー関数で、出力内容を自動的にgzip圧縮してブラウザに送信する機能を提供します。
基本構文
string ob_gzhandler(string $buffer, int $mode)
- パラメータ:
$buffer
: 圧縮するバッファ内容$mode
: 出力バッファのモード(通常は自動設定)
- 戻り値: 圧縮された内容、または圧縮できない場合は元の内容
圧縮の仕組み
- ブラウザ対応チェック: Accept-Encodingヘッダーを確認
- 圧縮実行: gzipまたはdeflate形式で圧縮
- ヘッダー設定: Content-Encodingヘッダーを自動設定
- 出力: 圧縮済みデータをブラウザに送信
基本的な使い方
シンプルな例
<?php
// 出力圧縮を有効にする
ob_start('ob_gzhandler');
?>
<!DOCTYPE html>
<html>
<head>
<title>圧縮テストページ</title>
<meta charset="UTF-8">
</head>
<body>
<h1>gzip圧縮テスト</h1>
<p>このページは自動的に圧縮されて送信されます。</p>
<?php
// 大量のテキストデータ(圧縮効果を確認するため)
for ($i = 1; $i <= 100; $i++) {
echo "<p>これは圧縮テスト用の繰り返しテキストです。行番号: {$i}</p>\n";
}
?>
<script>
// JavaScript も圧縮される
console.log('このJavaScriptコードも圧縮されます');
console.log('圧縮率を確認するために長いコメントを追加しています。' +
'gzipは繰り返しパターンや類似テキストを効率的に圧縮します。' +
'実際のWebページでは30-80%の圧縮率を期待できます。');
</script>
</body>
</html>
<?php
// バッファを出力して終了
ob_end_flush();
?>
圧縮効果の確認
<?php
// 圧縮前後のサイズを比較する例
function demonstrateCompression() {
// テストデータを生成
$testData = str_repeat("これは圧縮テスト用の繰り返しデータです。", 1000);
$testData .= "\n" . str_repeat("<p>HTMLタグも含めた圧縮テストです。</p>", 500);
// 元のサイズ
$originalSize = strlen($testData);
// ob_gzhandlerを使用して圧縮
ob_start('ob_gzhandler');
echo $testData;
$compressedContent = ob_get_contents();
ob_end_clean();
// 手動でgzip圧縮(比較用)
$manualGzip = gzencode($testData);
echo "<h2>圧縮効果の比較</h2>\n";
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>\n";
echo "<tr><th>項目</th><th>サイズ</th><th>圧縮率</th></tr>\n";
echo "<tr><td>元のサイズ</td><td>" . number_format($originalSize) . " bytes</td><td>-</td></tr>\n";
if (function_exists('gzencode')) {
$gzipSize = strlen($manualGzip);
$compressionRatio = round((1 - $gzipSize / $originalSize) * 100, 1);
echo "<tr><td>gzip圧縮後</td><td>" . number_format($gzipSize) . " bytes</td><td>{$compressionRatio}%削減</td></tr>\n";
}
echo "<tr><td>転送時間削減<br>(1Mbps接続)</td><td colspan='2'>";
if (function_exists('gzencode')) {
$timeSaved = ($originalSize - $gzipSize) * 8 / 1000000; // 秒
echo number_format($timeSaved, 3) . " 秒短縮";
} else {
echo "gzip拡張が必要";
}
echo "</td></tr>\n";
echo "</table>\n";
// ブラウザ情報を表示
echo "<h3>ブラウザ対応情報</h3>\n";
echo "<p><strong>Accept-Encoding:</strong> " . ($_SERVER['HTTP_ACCEPT_ENCODING'] ?? 'なし') . "</p>\n";
echo "<p><strong>User-Agent:</strong> " . ($_SERVER['HTTP_USER_AGENT'] ?? 'なし') . "</p>\n";
}
ob_start('ob_gzhandler');
?>
<!DOCTYPE html>
<html>
<head>
<title>圧縮効果テスト</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { margin: 20px 0; }
th, td { padding: 8px 12px; text-align: left; }
th { background-color: #f0f0f0; }
</style>
</head>
<body>
<h1>ob_gzhandler 圧縮効果デモ</h1>
<?php demonstrateCompression(); ?>
</body>
</html>
<?php
ob_end_flush();
?>
実践的な活用例
1. 高性能Webサイト構築システム
<?php
class PerformanceOptimizer {
private $compressionEnabled = false;
private $compressionLevel = 6;
private $minSizeForCompression = 1024; // 1KB
private $statistics = [
'original_size' => 0,
'compressed_size' => 0,
'compression_time' => 0
];
public function __construct($config = []) {
$this->compressionLevel = $config['compression_level'] ?? 6;
$this->minSizeForCompression = $config['min_size'] ?? 1024;
// ブラウザ対応とサーバー環境をチェック
$this->compressionEnabled = $this->canUseCompression();
}
public function startOptimizedOutput() {
if ($this->compressionEnabled) {
// カスタムハンドラーで詳細制御
ob_start([$this, 'compressionHandler']);
} else {
// 圧縮無効の場合は通常のバッファリング
ob_start();
}
// パフォーマンス用ヘッダーを設定
$this->setPerformanceHeaders();
}
public function compressionHandler($buffer, $mode) {
$startTime = microtime(true);
$originalSize = strlen($buffer);
// 最小サイズチェック
if ($originalSize < $this->minSizeForCompression) {
return $buffer;
}
// コンテンツタイプチェック
if (!$this->shouldCompress()) {
return $buffer;
}
// ブラウザ対応チェック
$encoding = $this->getBestEncoding();
if (!$encoding) {
return $buffer;
}
// 圧縮実行
$compressed = $this->compressContent($buffer, $encoding);
if ($compressed !== false) {
$compressedSize = strlen($compressed);
$compressionTime = microtime(true) - $startTime;
// 統計情報を更新
$this->updateStatistics($originalSize, $compressedSize, $compressionTime);
// ヘッダー設定
$this->setCompressionHeaders($encoding, $originalSize, $compressedSize);
// パフォーマンスログ
$this->logPerformance($originalSize, $compressedSize, $compressionTime);
return $compressed;
}
return $buffer;
}
private function canUseCompression() {
// PHP拡張チェック
if (!function_exists('gzencode') && !function_exists('gzcompress')) {
return false;
}
// ブラウザ対応チェック
$acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
if (empty($acceptEncoding)) {
return false;
}
// 既に圧縮されているかチェック
if (headers_sent()) {
return false;
}
return true;
}
private function getBestEncoding() {
$acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
// gzipを優先、次にdeflate
if (strpos($acceptEncoding, 'gzip') !== false && function_exists('gzencode')) {
return 'gzip';
}
if (strpos($acceptEncoding, 'deflate') !== false && function_exists('gzcompress')) {
return 'deflate';
}
return false;
}
private function shouldCompress() {
$contentType = '';
// 送信済みヘッダーから Content-Type を取得
$headers = headers_list();
foreach ($headers as $header) {
if (stripos($header, 'content-type:') === 0) {
$contentType = strtolower($header);
break;
}
}
// デフォルトでHTMLとして扱う
if (empty($contentType)) {
$contentType = 'text/html';
}
// 圧縮すべきコンテンツタイプ
$compressibleTypes = [
'text/html',
'text/css',
'text/javascript',
'application/javascript',
'application/json',
'application/xml',
'text/xml',
'text/plain'
];
foreach ($compressibleTypes as $type) {
if (strpos($contentType, $type) !== false) {
return true;
}
}
return false;
}
private function compressContent($content, $encoding) {
switch ($encoding) {
case 'gzip':
return gzencode($content, $this->compressionLevel);
case 'deflate':
return gzcompress($content, $this->compressionLevel);
default:
return false;
}
}
private function setCompressionHeaders($encoding, $originalSize, $compressedSize) {
header("Content-Encoding: {$encoding}");
header("Content-Length: {$compressedSize}");
header("X-Original-Size: {$originalSize}");
header("X-Compressed-Size: {$compressedSize}");
header("X-Compression-Ratio: " . round((1 - $compressedSize / $originalSize) * 100, 1) . "%");
// Varyヘッダーでキャッシュを適切に制御
header("Vary: Accept-Encoding");
}
private function setPerformanceHeaders() {
// キャッシュ制御
header("Cache-Control: public, max-age=3600");
header("ETag: " . md5($_SERVER['REQUEST_URI'] . filemtime(__FILE__)));
// 圧縮を示すヘッダー
header("X-Powered-By: PHP-GzHandler");
}
private function updateStatistics($originalSize, $compressedSize, $compressionTime) {
$this->statistics['original_size'] += $originalSize;
$this->statistics['compressed_size'] += $compressedSize;
$this->statistics['compression_time'] += $compressionTime;
}
private function logPerformance($originalSize, $compressedSize, $compressionTime) {
$compressionRatio = round((1 - $compressedSize / $originalSize) * 100, 1);
$throughput = $originalSize / max($compressionTime, 0.001); // MB/s
error_log(sprintf(
"Compression: %d->%d bytes (%.1f%%) in %.4fs (%.2f MB/s)",
$originalSize,
$compressedSize,
$compressionRatio,
$compressionTime,
$throughput / (1024 * 1024)
));
}
public function endOptimizedOutput() {
ob_end_flush();
// 統計情報をヘッダーに追加(デバッグ用)
if (!headers_sent() && defined('DEBUG') && DEBUG) {
$totalOriginal = $this->statistics['original_size'];
$totalCompressed = $this->statistics['compressed_size'];
if ($totalOriginal > 0) {
$overallRatio = round((1 - $totalCompressed / $totalOriginal) * 100, 1);
header("X-Total-Original-Size: {$totalOriginal}");
header("X-Total-Compressed-Size: {$totalCompressed}");
header("X-Overall-Compression-Ratio: {$overallRatio}%");
header("X-Compression-Time: " . round($this->statistics['compression_time'] * 1000, 2) . "ms");
}
}
}
public function getStatistics() {
return $this->statistics;
}
public function isCompressionEnabled() {
return $this->compressionEnabled;
}
}
// 使用例:高性能Webページ
$optimizer = new PerformanceOptimizer([
'compression_level' => 6,
'min_size' => 512
]);
$optimizer->startOptimizedOutput();
?>
<!DOCTYPE html>
<html>
<head>
<title>高性能Webサイトデモ</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container { max-width: 1200px; margin: 0 auto; }
.card {
background: rgba(255,255,255,0.1);
padding: 20px;
margin: 20px 0;
border-radius: 10px;
backdrop-filter: blur(10px);
}
.stats { display: flex; justify-content: space-between; flex-wrap: wrap; }
.stat { text-align: center; padding: 15px; }
pre { background: rgba(0,0,0,0.3); padding: 15px; border-radius: 5px; overflow-x: auto; }
@media (max-width: 768px) {
.stats { flex-direction: column; }
body { padding: 10px; }
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 高性能圧縮Webサイト</h1>
<div class="card">
<h2>圧縮統計情報</h2>
<div class="stats">
<div class="stat">
<h3>圧縮状態</h3>
<p><?php echo $optimizer->isCompressionEnabled() ? '✅ 有効' : '❌ 無効'; ?></p>
</div>
<div class="stat">
<h3>ブラウザ対応</h3>
<p><?php echo $_SERVER['HTTP_ACCEPT_ENCODING'] ?? 'なし'; ?></p>
</div>
<div class="stat">
<h3>圧縮形式</h3>
<p><?php
$encoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
if (strpos($encoding, 'gzip') !== false) echo 'gzip';
elseif (strpos($encoding, 'deflate') !== false) echo 'deflate';
else echo 'なし';
?></p>
</div>
</div>
</div>
<div class="card">
<h2>パフォーマンスデータ</h2>
<?php
// 大量のサンプルデータを生成(圧縮効果を実感するため)
echo "<p>以下は圧縮効果を示すためのサンプルデータです:</p>";
for ($i = 1; $i <= 50; $i++) {
echo "<p>行 {$i}: この行は圧縮テスト用のサンプルテキストです。繰り返し要素が多いほど圧縮効果が高くなります。";
echo "gzip圧縮は、同じパターンの繰り返しや似たような文字列を効率的に圧縮することができます。</p>\n";
}
?>
</div>
<div class="card">
<h2>技術情報</h2>
<pre><?php
echo "サーバー情報:\n";
echo "PHP バージョン: " . PHP_VERSION . "\n";
echo "zlib サポート: " . (extension_loaded('zlib') ? 'Yes' : 'No') . "\n";
echo "gzip 関数: " . (function_exists('gzencode') ? 'Available' : 'Not available') . "\n";
echo "deflate 関数: " . (function_exists('gzcompress') ? 'Available' : 'Not available') . "\n";
echo "メモリ使用量: " . round(memory_get_usage() / 1024 / 1024, 2) . " MB\n";
echo "ピークメモリ: " . round(memory_get_peak_usage() / 1024 / 1024, 2) . " MB\n";
?></pre>
</div>
</div>
<script>
// JavaScript も一緒に圧縮される
console.log('このJavaScriptコードも圧縮されて転送されます');
// パフォーマンス測定
window.addEventListener('load', function() {
const loadTime = performance.now();
console.log(`ページロード時間: ${loadTime.toFixed(2)}ms`);
// 転送サイズの情報を表示(可能な場合)
if (performance.getEntriesByType) {
const entries = performance.getEntriesByType('navigation');
if (entries.length > 0) {
const entry = entries[0];
console.log(`転送サイズ: ${entry.transferSize} bytes`);
console.log(`リソースサイズ: ${entry.decodedBodySize} bytes`);
if (entry.decodedBodySize > entry.transferSize) {
const ratio = ((entry.decodedBodySize - entry.transferSize) / entry.decodedBodySize * 100).toFixed(1);
console.log(`圧縮率: ${ratio}%`);
}
}
}
});
</script>
</body>
</html>
<?php
$optimizer->endOptimizedOutput();
// 統計情報をログ出力(本番環境では適宜調整)
$stats = $optimizer->getStatistics();
if ($stats['original_size'] > 0) {
$ratio = round((1 - $stats['compressed_size'] / $stats['original_size']) * 100, 1);
error_log("Page compression stats - Original: {$stats['original_size']}B, Compressed: {$stats['compressed_size']}B, Ratio: {$ratio}%");
}
?>
2. APIレスポンス圧縮システム
<?php
class CompressedApiHandler {
private $supportedFormats = ['json', 'xml', 'text'];
private $compressionThreshold = 1024; // 1KB
public function handleRequest($endpoint, $data = null) {
// リクエスト解析
$format = $this->getResponseFormat();
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
// 圧縮対応ブラウザかチェック
$supportsCompression = $this->clientSupportsCompression();
// レスポンス生成開始
if ($supportsCompression && in_array($format, $this->supportedFormats)) {
ob_start('ob_gzhandler');
} else {
ob_start();
}
// 適切なContent-Typeヘッダーを設定
$this->setContentTypeHeader($format);
// APIレスポンス生成
$response = $this->generateResponse($endpoint, $method, $data);
// レスポンス出力
echo $response;
// バッファを終了
ob_end_flush();
// ログ記録
$this->logApiCall($endpoint, $method, strlen($response), $supportsCompression);
}
private function getResponseFormat() {
// Accept ヘッダーまたは URL パラメータから形式を判定
$accept = $_SERVER['HTTP_ACCEPT'] ?? '';
$format = $_GET['format'] ?? '';
if ($format) {
return strtolower($format);
}
if (strpos($accept, 'application/json') !== false) {
return 'json';
} elseif (strpos($accept, 'application/xml') !== false || strpos($accept, 'text/xml') !== false) {
return 'xml';
} else {
return 'json'; // デフォルト
}
}
private function clientSupportsCompression() {
$acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
return (strpos($acceptEncoding, 'gzip') !== false || strpos($acceptEncoding, 'deflate') !== false);
}
private function setContentTypeHeader($format) {
switch ($format) {
case 'json':
header('Content-Type: application/json; charset=utf-8');
break;
case 'xml':
header('Content-Type: application/xml; charset=utf-8');
break;
case 'text':
header('Content-Type: text/plain; charset=utf-8');
break;
}
// CORS対応
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
}
private function generateResponse($endpoint, $method, $data) {
// 実際のAPIロジック(例)
$responseData = $this->processApiRequest($endpoint, $method, $data);
$format = $this->getResponseFormat();
switch ($format) {
case 'json':
return json_encode($responseData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
case 'xml':
return $this->arrayToXml($responseData);
case 'text':
return $this->arrayToText($responseData);
default:
return json_encode(['error' => 'Unsupported format'], JSON_UNESCAPED_UNICODE);
}
}
private function processApiRequest($endpoint, $method, $data) {
// 実際のビジネスロジック(ダミー実装)
switch ($endpoint) {
case 'users':
return $this->getUsersData();
case 'products':
return $this->getProductsData();
case 'analytics':
return $this->getAnalyticsData();
default:
return [
'error' => 'Endpoint not found',
'available_endpoints' => ['users', 'products', 'analytics']
];
}
}
private function getUsersData() {
// 大量のユーザーデータ(圧縮効果を示すため)
$users = [];
for ($i = 1; $i <= 1000; $i++) {
$users[] = [
'id' => $i,
'name' => "ユーザー {$i}",
'email' => "user{$i}@example.com",
'status' => $i % 3 === 0 ? 'inactive' : 'active',
'created_at' => date('Y-m-d H:i:s', strtotime("-{$i} days")),
'profile' => [
'bio' => "これはユーザー{$i}のプロフィール情報です。圧縮効果を確認するための重複するテキスト内容が含まれています。",
'preferences' => [
'language' => 'ja',
'timezone' => 'Asia/Tokyo',
'notifications' => true
]
]
];
}
return [
'status' => 'success',
'total' => count($users),
'data' => $users,
'metadata' => [
'generated_at' => date('c'),
'compression_info' => [
'client_supports' => $this->clientSupportsCompression(),
'content_type' => $this->getResponseFormat()
]
]
];
}
private function getProductsData() {
$products = [];
for ($i = 1; $i <= 500; $i++) {
$products[] = [
'id' => $i,
'name' => "商品 {$i}",
'description' => "これは商品{$i}の詳細説明です。高品質な素材を使用し、優れた機能性を提供します。圧縮テスト用の繰り返しテキストも含まれています。",
'price' => rand(100, 50000),
'category' => ['electronics', 'clothing', 'books', 'home'][rand(0, 3)],
'tags' => ['人気', '新商品', '限定', 'セール'][rand(0, 3)],
'specifications' => [
'weight' => rand(100, 5000) . 'g',
'dimensions' => rand(10, 100) . 'x' . rand(10, 100) . 'x' . rand(10, 100) . 'cm',
'material' => ['プラスチック', '金属', '木材', '繊維'][rand(0, 3)]
]
];
}
return [
'status' => 'success',
'total' => count($products),
'data' => $products
];
}
private function getAnalyticsData() {
$analytics = [];
for ($i = 0; $i < 365; $i++) {
$date = date('Y-m-d', strtotime("-{$i} days"));
$analytics[] = [
'date' => $date,
'page_views' => rand(1000, 10000),
'unique_visitors' => rand(500, 5000),
'bounce_rate' => rand(20, 80) / 100,
'avg_session_duration' => rand(60, 600),
'top_pages' => [
'/' => rand(100, 1000),
'/products' => rand(50, 500),
'/about' => rand(20, 200),
'/contact' => rand(10, 100)
],
'referrers' => [
'google.com' => rand(40, 60),
'facebook.com' => rand(10, 30),
'twitter.com' => rand(5, 20),
'direct' => rand(20, 40)
]
];
}
return [
'status' => 'success',
'period' => '365 days',
'data' => $analytics,
'summary' => [
'total_page_views' => array_sum(array_column($analytics, 'page_views')),
'total_unique_visitors' => array_sum(array_column($analytics, 'unique_visitors')),
'average_bounce_rate' => round(array_sum(array_column($analytics, 'bounce_rate')) / count($analytics), 3),
'data_size_info' => [
'uncompressed_estimate' => strlen(json_encode($analytics)) . ' bytes',
'compression_supported' => $this->clientSupportsCompression()
]
]
];
}
private function arrayToXml($array, $rootElement = 'response', $xml = null) {
if ($xml === null) {
$xml = new SimpleXMLElement("<{$rootElement}></{$rootElement}>");
}
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->arrayToXml($value, $key, $xml->addChild($key));
} else {
$xml->addChild($key, htmlspecialchars($value));
}
}
return $xml->asXML();
}
private function arrayToText($array, $indent = 0) {
$text = '';
$prefix = str_repeat(' ', $indent);
foreach ($array as $key => $value) {
if (is_array($value)) {
$text .= $prefix . $key . ":\n";
$text .= $this->arrayToText($value, $indent + 1);
} else {
$text .= $prefix . $key . ': ' . $value . "\n";
}
}
return $text;
}
private function logApiCall($endpoint, $method, $responseSize, $compressed) {
$logEntry = [
'timestamp' => date('c'),
'endpoint' => $endpoint,
'method' => $method,
'response_size' => $responseSize,
'compressed' => $compressed,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'Unknown'
];
error_log('API Call: ' . json_encode($logEntry));
}
}
// APIエンドポイントの使用例
$api = new CompressedApiHandler();
// ルーティング(簡単な例)
$endpoint = $_GET['endpoint'] ?? 'users';
$api->handleRequest($endpoint);
?>
3. 条件付き圧縮システム
<?php
class ConditionalCompressionManager {
private $config = [
'enable_compression' => true,
'compression_level' => 6,
'min_size_threshold' => 1024,
'max_size_threshold' => 10 * 1024 * 1024, // 10MB
'cpu_threshold' => 80, // CPU使用率80%以下で圧縮
'memory_threshold' => 80, // メモリ使用率80%以下で圧縮
'exclude_user_agents' => ['bot', 'crawler', 'spider'],
'exclude_ips' => ['127.0.0.1'],
'force_compression_ips' => [], // 常に圧縮するIP
'content_types' => [
'text/html' => true,
'text/css' => true,
'text/javascript' => true,
'application/javascript' => true,
'application/json' => true,
'application/xml' => true,
'text/xml' => true,
'text/plain' => true,
'image/svg+xml' => true
]
];
private $stats = [
'requests_total' => 0,
'requests_compressed' => 0,
'bytes_saved' => 0,
'compression_time' => 0
];
public function __construct($config = []) {
$this->config = array_merge($this->config, $config);
}
public function shouldCompress($contentSize = null, $contentType = null) {
$this->stats['requests_total']++;
// 基本設定チェック
if (!$this->config['enable_compression']) {
return false;
}
// PHP拡張チェック
if (!function_exists('ob_gzhandler')) {
return false;
}
// ブラウザ対応チェック
if (!$this->clientSupportsCompression()) {
return false;
}
// IPアドレスチェック
if (!$this->isIpAllowed()) {
return false;
}
// User-Agentチェック
if (!$this->isUserAgentAllowed()) {
return false;
}
// コンテンツタイプチェック
if ($contentType && !$this->isContentTypeCompressible($contentType)) {
return false;
}
// サイズチェック
if ($contentSize !== null && !$this->isSizeAppropriate($contentSize)) {
return false;
}
// システムリソースチェック
if (!$this->hasEnoughResources()) {
return false;
}
return true;
}
public function startConditionalCompression() {
if ($this->shouldCompress()) {
ob_start([$this, 'compressionHandler']);
return true;
} else {
ob_start();
return false;
}
}
public function compressionHandler($buffer, $mode) {
$startTime = microtime(true);
$originalSize = strlen($buffer);
// 実際の圧縮実行時にも再チェック
if (!$this->shouldCompress($originalSize)) {
return $buffer;
}
// Content-Typeの最終チェック
$contentType = $this->getCurrentContentType();
if (!$this->isContentTypeCompressible($contentType)) {
return $buffer;
}
// 圧縮実行
$compressed = $this->performCompression($buffer);
if ($compressed !== false) {
$compressionTime = microtime(true) - $startTime;
$compressedSize = strlen($compressed);
$bytesSaved = $originalSize - $compressedSize;
// 統計更新
$this->updateStats($bytesSaved, $compressionTime);
// ヘッダー設定
$this->setCompressionHeaders($originalSize, $compressedSize);
// ログ記録
$this->logCompression($originalSize, $compressedSize, $compressionTime);
return $compressed;
}
return $buffer;
}
private function clientSupportsCompression() {
$acceptEncoding = strtolower($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '');
return (strpos($acceptEncoding, 'gzip') !== false || strpos($acceptEncoding, 'deflate') !== false);
}
private function isIpAllowed() {
$clientIp = $this->getClientIp();
// 強制圧縮IPリスト
if (in_array($clientIp, $this->config['force_compression_ips'])) {
return true;
}
// 除外IPリスト
if (in_array($clientIp, $this->config['exclude_ips'])) {
return false;
}
return true;
}
private function isUserAgentAllowed() {
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
foreach ($this->config['exclude_user_agents'] as $excluded) {
if (strpos($userAgent, $excluded) !== false) {
return false;
}
}
return true;
}
private function isContentTypeCompressible($contentType) {
$contentType = strtolower(trim($contentType));
// セミコロンで区切られている場合は最初の部分のみを使用
if (strpos($contentType, ';') !== false) {
$contentType = trim(explode(';', $contentType)[0]);
}
return isset($this->config['content_types'][$contentType]) &&
$this->config['content_types'][$contentType];
}
private function isSizeAppropriate($size) {
return ($size >= $this->config['min_size_threshold'] &&
$size <= $this->config['max_size_threshold']);
}
private function hasEnoughResources() {
// CPU使用率チェック(簡易版)
$load = sys_getloadavg();
if ($load && $load[0] > $this->config['cpu_threshold'] / 100 * 4) { // 4コア想定
return false;
}
// メモリ使用率チェック
$memoryUsage = memory_get_usage();
$memoryLimit = ini_get('memory_limit');
if ($memoryLimit !== '-1') {
$memoryLimitBytes = $this->parseMemorySize($memoryLimit);
$memoryUsagePercent = ($memoryUsage / $memoryLimitBytes) * 100;
if ($memoryUsagePercent > $this->config['memory_threshold']) {
return false;
}
}
return true;
}
private function getCurrentContentType() {
$headers = headers_list();
foreach ($headers as $header) {
if (stripos($header, 'content-type:') === 0) {
return substr($header, 13); // "Content-Type:"の後
}
}
return 'text/html'; // デフォルト
}
private function performCompression($buffer) {
$acceptEncoding = strtolower($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '');
if (strpos($acceptEncoding, 'gzip') !== false && function_exists('gzencode')) {
return gzencode($buffer, $this->config['compression_level']);
} elseif (strpos($acceptEncoding, 'deflate') !== false && function_exists('gzcompress')) {
return gzcompress($buffer, $this->config['compression_level']);
}
return false;
}
private function setCompressionHeaders($originalSize, $compressedSize) {
$acceptEncoding = strtolower($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '');
if (strpos($acceptEncoding, 'gzip') !== false) {
header('Content-Encoding: gzip');
} elseif (strpos($acceptEncoding, 'deflate') !== false) {
header('Content-Encoding: deflate');
}
header('Vary: Accept-Encoding');
header('Content-Length: ' . $compressedSize);
// 統計ヘッダー(開発/デバッグ用)
if (defined('DEBUG') && DEBUG) {
$compressionRatio = round((1 - $compressedSize / $originalSize) * 100, 1);
header("X-Compression-Ratio: {$compressionRatio}%");
header("X-Original-Size: {$originalSize}");
header("X-Compressed-Size: {$compressedSize}");
}
}
private function updateStats($bytesSaved, $compressionTime) {
$this->stats['requests_compressed']++;
$this->stats['bytes_saved'] += $bytesSaved;
$this->stats['compression_time'] += $compressionTime;
}
private function logCompression($originalSize, $compressedSize, $compressionTime) {
$compressionRatio = round((1 - $compressedSize / $originalSize) * 100, 1);
error_log(sprintf(
"Conditional compression: %s -> %s (%.1f%%) in %.4fs | Client: %s | URL: %s",
$this->formatBytes($originalSize),
$this->formatBytes($compressedSize),
$compressionRatio,
$compressionTime,
$_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
$_SERVER['REQUEST_URI'] ?? 'Unknown'
));
}
private function getClientIp() {
$headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
foreach ($headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
return trim($ips[0]);
}
}
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}
private function parseMemorySize($size) {
$unit = strtoupper(substr($size, -1));
$value = (int)substr($size, 0, -1);
switch ($unit) {
case 'G': return $value * 1024 * 1024 * 1024;
case 'M': return $value * 1024 * 1024;
case 'K': return $value * 1024;
default: return (int)$size;
}
}
private function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB'];
$power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
return round($bytes / pow(1024, $power), 2) . ' ' . $units[$power];
}
public function getStatistics() {
$compressionRate = $this->stats['requests_total'] > 0 ?
round(($this->stats['requests_compressed'] / $this->stats['requests_total']) * 100, 1) : 0;
return [
'requests_total' => $this->stats['requests_total'],
'requests_compressed' => $this->stats['requests_compressed'],
'compression_rate' => $compressionRate . '%',
'bytes_saved' => $this->formatBytes($this->stats['bytes_saved']),
'avg_compression_time' => $this->stats['requests_compressed'] > 0 ?
round($this->stats['compression_time'] / $this->stats['requests_compressed'] * 1000, 2) . 'ms' : '0ms'
];
}
public function generateReport() {
return [
'config' => $this->config,
'statistics' => $this->getStatistics(),
'system_info' => [
'php_version' => PHP_VERSION,
'zlib_loaded' => extension_loaded('zlib'),
'gzip_available' => function_exists('gzencode'),
'deflate_available' => function_exists('gzcompress'),
'memory_usage' => $this->formatBytes(memory_get_usage()),
'memory_peak' => $this->formatBytes(memory_get_peak_usage()),
'server_load' => sys_getloadavg()
]
];
}
}
// 使用例:条件付き圧縮の実装
$compressionManager = new ConditionalCompressionManager([
'compression_level' => 6,
'min_size_threshold' => 512,
'cpu_threshold' => 70,
'exclude_user_agents' => ['googlebot', 'bingbot']
]);
$isCompressing = $compressionManager->startConditionalCompression();
// Content-Typeを設定
header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html>
<head>
<title>条件付き圧縮デモ</title>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.info-box { background: #f0f8ff; padding: 15px; margin: 10px 0; border-left: 5px solid #0066cc; }
.stats-table { border-collapse: collapse; width: 100%; margin: 20px 0; }
.stats-table th, .stats-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.stats-table th { background-color: #f2f2f2; }
.compressed { color: green; font-weight: bold; }
.not-compressed { color: orange; font-weight: bold; }
</style>
</head>
<body>
<h1>🔧 条件付き圧縮システム</h1>
<div class="info-box">
<h3>圧縮状態</h3>
<p>現在のリクエスト: <span class="<?php echo $isCompressing ? 'compressed' : 'not-compressed'; ?>">
<?php echo $isCompressing ? '✅ 圧縮中' : '❌ 圧縮なし'; ?>
</span></p>
</div>
<?php
$report = $compressionManager->generateReport();
?>
<h2>システム情報</h2>
<table class="stats-table">
<tr><th>項目</th><th>値</th></tr>
<tr><td>PHP バージョン</td><td><?php echo $report['system_info']['php_version']; ?></td></tr>
<tr><td>zlib 拡張</td><td><?php echo $report['system_info']['zlib_loaded'] ? '✅ ロード済み' : '❌ 未ロード'; ?></td></tr>
<tr><td>gzip サポート</td><td><?php echo $report['system_info']['gzip_available'] ? '✅ 利用可能' : '❌ 利用不可'; ?></td></tr>
<tr><td>deflate サポート</td><td><?php echo $report['system_info']['deflate_available'] ? '✅ 利用可能' : '❌ 利用不可'; ?></td></tr>
<tr><td>メモリ使用量</td><td><?php echo $report['system_info']['memory_usage']; ?></td></tr>
<tr><td>ピークメモリ</td><td><?php echo $report['system_info']['memory_peak']; ?></td></tr>
</table>
<h2>圧縮統計</h2>
<table class="stats-table">
<?php $stats = $report['statistics']; ?>
<tr><th>項目</th><th>値</th></tr>
<tr><td>総リクエスト数</td><td><?php echo $stats['requests_total']; ?></td></tr>
<tr><td>圧縮リクエスト数</td><td><?php echo $stats['requests_compressed']; ?></td></tr>
<tr><td>圧縮率</td><td><?php echo $stats['compression_rate']; ?></td></tr>
<tr><td>節約バイト数</td><td><?php echo $stats['bytes_saved']; ?></td></tr>
<tr><td>平均圧縮時間</td><td><?php echo $stats['avg_compression_time']; ?></td></tr>
</table>
<h2>設定情報</h2>
<table class="stats-table">
<?php $config = $report['config']; ?>
<tr><th>設定項目</th><th>値</th></tr>
<tr><td>圧縮有効</td><td><?php echo $config['enable_compression'] ? '✅ 有効' : '❌ 無効'; ?></td></tr>
<tr><td>圧縮レベル</td><td><?php echo $config['compression_level']; ?></td></tr>
<tr><td>最小サイズ閾値</td><td><?php echo number_format($config['min_size_threshold']); ?> bytes</td></tr>
<tr><td>最大サイズ閾値</td><td><?php echo number_format($config['max_size_threshold']); ?> bytes</td></tr>
<tr><td>CPU閾値</td><td><?php echo $config['cpu_threshold']; ?>%</td></tr>
<tr><td>メモリ閾値</td><td><?php echo $config['memory_threshold']; ?>%</td></tr>
</table>
<h2>クライアント情報</h2>
<table class="stats-table">
<tr><th>項目</th><th>値</th></tr>
<tr><td>Accept-Encoding</td><td><?php echo htmlspecialchars($_SERVER['HTTP_ACCEPT_ENCODING'] ?? 'なし'); ?></td></tr>
<tr><td>User-Agent</td><td><?php echo htmlspecialchars(substr($_SERVER['HTTP_USER_AGENT'] ?? 'なし', 0, 100)); ?></td></tr>
<tr><td>IPアドレス</td><td><?php echo htmlspecialchars($_SERVER['REMOTE_ADDR'] ?? 'なし'); ?></td></tr>
</table>
<div class="info-box">
<h3>💡 圧縮のメリット</h3>
<ul>
<li><strong>転送量削減:</strong> 通常30-80%のデータ量削減</li>
<li><strong>表示速度向上:</strong> 特に低速回線で効果大</li>
<li><strong>サーバー負荷軽減:</strong> 帯域幅の節約</li>
<li><strong>SEO効果:</strong> ページ速度の改善</li>
</ul>
</div>
<?php
// 圧縮効果を示すためのサンプルデータ
echo "<h2>サンプルデータ(圧縮対象)</h2>";
echo "<div style='max-height: 200px; overflow-y: auto; border: 1px solid #ccc; padding: 10px;'>";
for ($i = 1; $i <= 100; $i++) {
echo "<p>行 {$i}: このテキストは圧縮効果を示すためのサンプルデータです。繰り返しが多いほど圧縮効果が高くなります。</p>";
}
echo "</div>";
?>
</body>
</html>
<?php
ob_end_flush();
// セッション統計を更新(実際のアプリケーションでは永続化)
echo "<!-- 圧縮統計: " . json_encode($compressionManager->getStatistics()) . " -->";
?>
注意点とベストプラクティス
1. パフォーマンス考慮事項
<?php
// 圧縮レベル別のパフォーマンス比較
function benchmarkCompressionLevels($testData) {
$results = [];
for ($level = 1; $level <= 9; $level++) {
$start = microtime(true);
// メモリ使用量測定開始
$memoryBefore = memory_get_usage();
// 圧縮実行
$compressed = gzencode($testData, $level);
// 測定終了
$compressionTime = microtime(true) - $start;
$memoryUsed = memory_get_usage() - $memoryBefore;
$results[] = [
'level' => $level,
'original_size' => strlen($testData),
'compressed_size' => strlen($compressed),
'compression_ratio' => round((1 - strlen($compressed) / strlen($testData)) * 100, 2),
'compression_time' => round($compressionTime * 1000, 2), // ms
'memory_used' => round($memoryUsed / 1024, 2), // KB
'throughput' => round(strlen($testData) / $compressionTime / 1024 / 1024, 2) // MB/s
];
}
return $results;
}
// テストデータ生成
$testData = str_repeat("圧縮テスト用のサンプルテキストです。", 1000);
$testData .= str_repeat("<p>HTMLタグを含むテキストデータ</p>", 500);
// ベンチマーク実行
$benchmarkResults = benchmarkCompressionLevels($testData);
echo "<h2>圧縮レベル別パフォーマンス</h2>";
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
echo "<tr><th>レベル</th><th>圧縮率</th><th>時間(ms)</th><th>メモリ(KB)</th><th>スループット(MB/s)</th><th>推奨用途</th></tr>";
foreach ($benchmarkResults as $result) {
$recommendation = '';
if ($result['level'] <= 3) {
$recommendation = '高トラフィック';
} elseif ($result['level'] <= 6) {
$recommendation = 'バランス型';
} else {
$recommendation = '高圧縮率';
}
echo "<tr>";
echo "<td>{$result['level']}</td>";
echo "<td>{$result['compression_ratio']}%</td>";
echo "<td>{$result['compression_time']}</td>";
echo "<td>{$result['memory_used']}</td>";
echo "<td>{$result['throughput']}</td>";
echo "<td>{$recommendation}</td>";
echo "</tr>";
}
echo "</table>";
?>
2. セキュリティ考慮事項
<?php
class SecureGzipHandler {
private $maxCompressionSize = 50 * 1024 * 1024; // 50MB
private $allowedContentTypes = [
'text/html',
'text/css',
'text/javascript',
'application/javascript',
'application/json'
];
public function secureCompress($buffer, $mode) {
// サイズ制限チェック(zip bomb攻撃対策)
if (strlen($buffer) > $this->maxCompressionSize) {
error_log("Compression blocked: Content too large (" . strlen($buffer) . " bytes)");
return $buffer;
}
// Content-Typeチェック
if (!$this->isAllowedContentType()) {
return $buffer;
}
// 悪意のあるコンテンツパターンをチェック
if ($this->containsMaliciousPatterns($buffer)) {
error_log("Compression blocked: Suspicious content detected");
return $buffer;
}
// レート制限チェック
if (!$this->isWithinRateLimit()) {
error_log("Compression blocked: Rate limit exceeded");
return $buffer;
}
// 安全な圧縮実行
return $this->performSecureCompression($buffer);
}
private function isAllowedContentType() {
$contentType = $this->getCurrentContentType();
foreach ($this->allowedContentTypes as $allowed) {
if (strpos($contentType, $allowed) !== false) {
return true;
}
}
return false;
}
private function containsMaliciousPatterns($buffer) {
// 基本的な悪意のあるパターンをチェック
$maliciousPatterns = [
'/\x00{100,}/', // null byte flooding
'/(.{1,10})\1{1000,}/', // excessive repetition
'/<script[^>]*>[\s\S]*?<\/script>/i' // script injection (basic)
];
foreach ($maliciousPatterns as $pattern) {
if (preg_match($pattern, $buffer)) {
return true;
}
}
return false;
}
private function isWithinRateLimit() {
$cacheKey = 'compression_rate_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
// 簡単なレート制限(実際のアプリケーションではRedisやMemcachedを使用)
$requestsFile = sys_get_temp_dir() . "/compression_requests.json";
$requests = [];
if (file_exists($requestsFile)) {
$requests = json_decode(file_get_contents($requestsFile), true) ?: [];
}
$now = time();
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
// 古いレコードを削除(1時間以上前)
$requests = array_filter($requests, function($timestamp) use ($now) {
return ($now - $timestamp) < 3600;
});
// 現在のIPのリクエスト数をチェック
$clientRequests = array_filter($requests, function($timestamp, $ip) use ($clientIp) {
return $ip === $clientIp;
}, ARRAY_FILTER_USE_BOTH);
// 1時間に100リクエストまで
if (count($clientRequests) >= 100) {
return false;
}
// リクエストを記録
$requests[$clientIp . '_' . $now] = $now;
file_put_contents($requestsFile, json_encode($requests));
return true;
}
private function performSecureCompression($buffer) {
$acceptEncoding = strtolower($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '');
try {
if (strpos($acceptEncoding, 'gzip') !== false) {
$compressed = gzencode($buffer, 6);
if ($compressed !== false) {
header('Content-Encoding: gzip');
header('Vary: Accept-Encoding');
return $compressed;
}
} elseif (strpos($acceptEncoding, 'deflate') !== false) {
$compressed = gzcompress($buffer, 6);
if ($compressed !== false) {
header('Content-Encoding: deflate');
header('Vary: Accept-Encoding');
return $compressed;
}
}
} catch (Exception $e) {
error_log("Compression error: " . $e->getMessage());
}
return $buffer;
}
private function getCurrentContentType() {
$headers = headers_list();
foreach ($headers as $header) {
if (stripos($header, 'content-type:') === 0) {
return strtolower(trim(substr($header, 13)));
}
}
return 'text/html';
}
}
// セキュアな圧縮の使用例
$secureHandler = new SecureGzipHandler();
ob_start([$secureHandler, 'secureCompress']);
echo "このコンテンツはセキュリティチェックを通過した場合のみ圧縮されます。";
ob_end_flush();
?>
3. トラブルシューティング
<?php
class GzipTroubleshooter {
public static function diagnoseCompressionIssues() {
$issues = [];
$recommendations = [];
// PHP拡張チェック
if (!extension_loaded('zlib')) {
$issues[] = "zlib拡張がロードされていません";
$recommendations[] = "php.iniでzlib拡張を有効にするか、PHPを再コンパイルしてください";
}
// 関数存在チェック
if (!function_exists('ob_gzhandler')) {
$issues[] = "ob_gzhandler関数が利用できません";
$recommendations[] = "PHPのzlibサポートを確認してください";
}
if (!function_exists('gzencode')) {
$issues[] = "gzencode関数が利用できません";
$recommendations[] = "zlib拡張の完全なインストールが必要です";
}
// 設定チェック
$outputBuffering = ini_get('output_buffering');
if ($outputBuffering && $outputBuffering !== 'Off') {
$issues[] = "output_bufferingが既に有効になっています";
$recommendations[] = "php.iniでoutput_buffering = Offに設定するか、適切に管理してください";
}
$zlibOutputCompression = ini_get('zlib.output_compression');
if ($zlibOutputCompression && $zlibOutputCompression !== 'Off') {
$issues[] = "zlib.output_compressionが既に有効です";
$recommendations[] = "重複圧縮を避けるため、どちらか一方のみ使用してください";
}
// ヘッダー送信チェック
if (headers_sent($filename, $line)) {
$issues[] = "ヘッダーが既に送信されています (ファイル: $filename, 行: $line)";
$recommendations[] = "ob_start()をHTMLやスペースの出力前に呼び出してください";
}
// メモリ制限チェック
$memoryLimit = ini_get('memory_limit');
if ($memoryLimit !== '-1') {
$memoryLimitBytes = self::parseMemorySize($memoryLimit);
if ($memoryLimitBytes < 64 * 1024 * 1024) { // 64MB
$issues[] = "メモリ制限が低すぎます ({$memoryLimit})";
$recommendations[] = "大きなコンテンツを圧縮する場合はメモリ制限を増やしてください";
}
}
return [
'issues' => $issues,
'recommendations' => $recommendations,
'system_info' => self::getSystemInfo()
];
}
public static function testCompression($testData = null) {
if ($testData === null) {
$testData = str_repeat("テスト用圧縮データ", 100);
}
$tests = [];
// ob_gzhandlerテスト
ob_start('ob_gzhandler');
echo $testData;
$compressed1 = ob_get_contents();
ob_end_clean();
$tests['ob_gzhandler'] = [
'success' => strlen($compressed1) > 0 && strlen($compressed1) < strlen($testData),
'original_size' => strlen($testData),
'compressed_size' => strlen($compressed1),
'compression_ratio' => strlen($compressed1) > 0 ?
round((1 - strlen($compressed1) / strlen($testData)) * 100, 2) : 0
];
// 手動gzip圧縮テスト
if (function_exists('gzencode')) {
$compressed2 = gzencode($testData);
$tests['gzencode'] = [
'success' => $compressed2 !== false,
'original_size' => strlen($testData),
'compressed_size' => strlen($compressed2),
'compression_ratio' => $compressed2 ?
round((1 - strlen($compressed2) / strlen($testData)) * 100, 2) : 0
];
}
// deflate圧縮テスト
if (function_exists('gzcompress')) {
$compressed3 = gzcompress($testData);
$tests['gzcompress'] = [
'success' => $compressed3 !== false,
'original_size' => strlen($testData),
'compressed_size' => strlen($compressed3),
'compression_ratio' => $compressed3 ?
round((1 - strlen($compressed3) / strlen($testData)) * 100, 2) : 0
];
}
return $tests;
}
public static function generateDiagnosticReport() {
$diagnosis = self::diagnoseCompressionIssues();
$compressionTests = self::testCompression();
$report = [
'timestamp' => date('Y-m-d H:i:s'),
'diagnosis' => $diagnosis,
'compression_tests' => $compressionTests,
'client_info' => [
'accepts_gzip' => strpos(($_SERVER['HTTP_ACCEPT_ENCODING'] ?? ''), 'gzip') !== false,
'accepts_deflate' => strpos(($_SERVER['HTTP_ACCEPT_ENCODING'] ?? ''), 'deflate') !== false,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
],
'recommendations' => self::generateOptimizationRecommendations($diagnosis, $compressionTests)
];
return $report;
}
private static function generateOptimizationRecommendations($diagnosis, $tests) {
$recommendations = [];
// 基本的な問題の推奨事項
if (!empty($diagnosis['issues'])) {
$recommendations[] = [
'priority' => 'high',
'category' => 'setup',
'message' => '基本設定に問題があります',
'actions' => $diagnosis['recommendations']
];
}
// 圧縮テスト結果に基づく推奨事項
$workingMethods = array_filter($tests, function($test) {
return $test['success'];
});
if (empty($workingMethods)) {
$recommendations[] = [
'priority' => 'critical',
'category' => 'functionality',
'message' => '圧縮が全く機能していません',
'actions' => ['PHP設定を確認', 'zlib拡張の再インストール', 'サーバー管理者に連絡']
];
} else {
// 最適な圧縮方法を推奨
$bestMethod = null;
$bestRatio = 0;
foreach ($workingMethods as $method => $result) {
if ($result['compression_ratio'] > $bestRatio) {
$bestRatio = $result['compression_ratio'];
$bestMethod = $method;
}
}
if ($bestMethod) {
$recommendations[] = [
'priority' => 'medium',
'category' => 'optimization',
'message' => "最適な圧縮方法は {$bestMethod} です(圧縮率: {$bestRatio}%)",
'actions' => ["実装時は {$bestMethod} を優先的に使用"]
];
}
}
return $recommendations;
}
private static function getSystemInfo() {
return [
'php_version' => PHP_VERSION,
'zlib_version' => extension_loaded('zlib') ? zlib_get_coding_type() : 'Not loaded',
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown',
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'output_buffering' => ini_get('output_buffering'),
'zlib_output_compression' => ini_get('zlib.output_compression'),
'functions_available' => [
'ob_gzhandler' => function_exists('ob_gzhandler'),
'gzencode' => function_exists('gzencode'),
'gzcompress' => function_exists('gzcompress'),
'gzdeflate' => function_exists('gzdeflate')
]
];
}
private static function parseMemorySize($size) {
$unit = strtoupper(substr($size, -1));
$value = (int)substr($size, 0, -1);
switch ($unit) {
case 'G': return $value * 1024 * 1024 * 1024;
case 'M': return $value * 1024 * 1024;
case 'K': return $value * 1024;
default: return (int)$size;
}
}
}
// 診断レポートの生成と表示
$diagnosticReport = GzipTroubleshooter::generateDiagnosticReport();
echo "<h2>🔧 Gzip圧縮 診断レポート</h2>";
// 問題の表示
if (!empty($diagnosticReport['diagnosis']['issues'])) {
echo "<h3 style='color: red;'>⚠️ 検出された問題</h3>";
echo "<ul>";
foreach ($diagnosticReport['diagnosis']['issues'] as $issue) {
echo "<li style='color: red;'>{$issue}</li>";
}
echo "</ul>";
echo "<h3 style='color: orange;'>💡 推奨事項</h3>";
echo "<ul>";
foreach ($diagnosticReport['diagnosis']['recommendations'] as $recommendation) {
echo "<li style='color: orange;'>{$recommendation}</li>";
}
echo "</ul>";
} else {
echo "<p style='color: green;'>✅ 基本設定に問題はありません</p>";
}
// 圧縮テスト結果
echo "<h3>🧪 圧縮テスト結果</h3>";
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
echo "<tr><th>方法</th><th>成功</th><th>元サイズ</th><th>圧縮後</th><th>圧縮率</th></tr>";
foreach ($diagnosticReport['compression_tests'] as $method => $result) {
$status = $result['success'] ? '✅' : '❌';
$statusColor = $result['success'] ? 'green' : 'red';
echo "<tr>";
echo "<td>{$method}</td>";
echo "<td style='color: {$statusColor};'>{$status}</td>";
echo "<td>" . number_format($result['original_size']) . "B</td>";
echo "<td>" . number_format($result['compressed_size']) . "B</td>";
echo "<td>{$result['compression_ratio']}%</td>";
echo "</tr>";
}
echo "</table>";
// クライアント情報
$clientInfo = $diagnosticReport['client_info'];
echo "<h3>📱 クライアント対応状況</h3>";
echo "<ul>";
echo "<li>gzip対応: " . ($clientInfo['accepts_gzip'] ? '✅' : '❌') . "</li>";
echo "<li>deflate対応: " . ($clientInfo['accepts_deflate'] ? '✅' : '❌') . "</li>";
echo "<li>User-Agent: " . htmlspecialchars(substr($clientInfo['user_agent'], 0, 100)) . "</li>";
echo "</ul>";
// 最適化推奨事項
if (!empty($diagnosticReport['recommendations'])) {
echo "<h3>🚀 最適化推奨事項</h3>";
foreach ($diagnosticReport['recommendations'] as $rec) {
$priorityColor = $rec['priority'] === 'critical' ? 'red' :
($rec['priority'] === 'high' ? 'orange' : 'blue');
echo "<div style='border-left: 5px solid {$priorityColor}; padding: 10px; margin: 10px 0;'>";
echo "<strong>[{$rec['priority']}] {$rec['message']}</strong><br>";
echo "<ul>";
foreach ($rec['actions'] as $action) {
echo "<li>{$action}</li>";
}
echo "</ul>";
echo "</div>";
}
}
?>
まとめ
ob_gzhandler
関数は、Webサイトのパフォーマンス向上において非常に強力なツールです。適切に実装することで、以下のような大きなメリットを得ることができます。
主なメリット
- 転送量削減: 通常30-80%のデータ量削減
- 表示速度向上: 特に低速回線での効果が顕著
- サーバー負荷軽減: 帯域幅の節約
- SEO効果: Core Web Vitalsの改善
実装のポイント
- 条件付き圧縮: システムリソースとクライアント対応を考慮
- セキュリティ: zip bomb攻撃やレート制限への対策
- パフォーマンス: 圧縮レベルと処理時間のバランス
- 監視: 圧縮効果の継続的な測定と最適化
実用的な応用
- 高性能Webサイト構築
- APIレスポンスの最適化
- 条件付き圧縮システム
- セキュリティを考慮した実装
適切に設定されたob_gzhandler
は、ユーザーエクスペリエンスの向上とシステムリソースの効率化を両立する、現代のWebアプリケーションにおいて必須の機能と言えるでしょう。
この記事で紹介した実装例を参考に、あなたのWebサイトやアプリケーションにも圧縮機能を導入してみてください。劇的なパフォーマンス改善が期待できます!