[PHP]zlib_decodeとは?圧縮されたデータを自動判別して解凍する方法を徹底解説

PHP

はじめに

Webアプリケーションでは、通信量を減らすためにデータをgzip形式やzlib形式で圧縮して扱う場面がよくあります。外部APIから圧縮されたレスポンスを受け取ったり、圧縮済みのデータをファイルやデータベースに保存していたりする場合、それを元の状態に戻す(解凍する)処理が必要になります。

zlib_decode() は、そうした圧縮データを解凍するための関数です。この関数の便利な点は、gzip形式・zlib形式・raw deflate形式のいずれであっても、ヘッダー情報を自動的に判別して適切に解凍してくれるという点にあります。gzuncompress()gzinflate() のように圧縮形式を限定する関数と異なり、zlib_decode() は汎用的な解凍関数として位置づけられています。本記事では基本的な使い方から実践的な活用例まで詳しく解説します。


関数概要

項目内容
関数名zlib_decode()
所属拡張zlib拡張(多くの環境で標準的に有効)
シグネチャzlib_decode(string $data, int $max_length = 0): string|false
引数1$data — 解凍対象の圧縮データ
引数2$max_length — 解凍後データの最大長(0で無制限)
戻り値解凍された文字列。失敗時は false
対応バージョンPHP 5.4以降
対応形式zlib、gzip、raw deflateを自動判別
対になる関数zlib_encode()(圧縮)

自動判別の仕組み(イメージ図)

  圧縮データ(形式不明)
              │
              ▼
        zlib_decode($data)
              │
   ┌──────────┴──────────────────┐
   │ データ先頭のマジックバイト(ヘッダー)  │
   │ を確認し、圧縮形式を自動判別する        │
   └──────────┬──────────────────┘
              ▼
   ┌──────────┬──────────┬──────────┐
   │ ZLIB形式    │ GZIP形式    │ RAW DEFLATE │
   │ (0x78で開始) │ (0x1F 0x8Bで開始)│ (ヘッダーなし)│
   └──────────┴──────────┴──────────┘
              │
              ▼
        元の展開されたデータ

ポイントは、gzuncompress()(zlib形式専用)、gzdecode()(gzip形式専用)、gzinflate()(raw deflate専用)と個別に使い分ける必要がある他の関数群と違い、zlib_decode()データの形式を意識せずに1つの関数で対応できるという利便性です。


実践サンプル7選

例1:基本的な使い方

<?php

class BasicZlibDecoder
{
    public function decode(string $compressed): string|false
    {
        // 圧縮形式を自動判別して解凍する
        return zlib_decode($compressed);
    }
}

$decoder = new BasicZlibDecoder();
$original = 'これはテストデータです。' . str_repeat('繰り返し', 10);
$compressed = zlib_encode($original, ZLIB_ENCODING_GZIP);
echo $decoder->decode($compressed) . PHP_EOL;

例2:外部APIから受け取った圧縮レスポンスを解凍するクラス

<?php

class CompressedApiResponseHandler
{
    /**
     * Content-Encodingヘッダーに応じて、
     * 圧縮されたレスポンスボディを解凍する
     */
    public function handleResponse(string $body, string $contentEncoding): string
    {
        if (in_array($contentEncoding, ['gzip', 'deflate'], true)) {
            $decoded = zlib_decode($body);
            if ($decoded === false) {
                throw new RuntimeException('レスポンスの解凍に失敗しました');
            }
            return $decoded;
        }

        return $body;
    }
}

$handler = new CompressedApiResponseHandler();
$compressed = zlib_encode('{"status":"ok"}', ZLIB_ENCODING_DEFLATE);
echo $handler->handleResponse($compressed, 'deflate') . PHP_EOL;

例3:解凍後のサイズ上限を指定して安全に処理するクラス

<?php

class SafeDecompressor
{
    /**
     * 第2引数で解凍後の最大サイズを指定し、
     * 巨大な展開(zip爆弾のような攻撃)からメモリを保護する
     */
    public function decodeWithLimit(string $compressed, int $maxBytes): string
    {
        $result = zlib_decode($compressed, $maxBytes);

        if ($result === false) {
            throw new RuntimeException('解凍に失敗しました(サイズ上限超過の可能性があります)');
        }

        return $result;
    }
}

$decompressor = new SafeDecompressor();
$compressed = zlib_encode(str_repeat('a', 1000), ZLIB_ENCODING_GZIP);
echo strlen($decompressor->decodeWithLimit($compressed, 2000)) . PHP_EOL;

例4:データベースに保存された圧縮データを読み出すクラス

<?php

class CompressedBlobReader
{
    public function __construct(private PDO $pdo)
    {
    }

    /**
     * ストレージ節約のため圧縮して保存していたBLOBデータを
     * 取得時に自動解凍する
     */
    public function readContent(int $id): ?string
    {
        $stmt = $this->pdo->prepare('SELECT compressed_content FROM documents WHERE id = :id');
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($row === false) {
            return null;
        }

        $result = zlib_decode($row['compressed_content']);
        return $result !== false ? $result : null;
    }
}

例5:解凍と圧縮の往復(ラウンドトリップ)を検証するテストツール

<?php

class RoundTripVerifier
{
    /**
     * zlib_encode()で圧縮したデータをzlib_decode()で解凍し、
     * 元のデータと一致するかを検証する
     */
    public function verify(string $original, int $encoding = ZLIB_ENCODING_GZIP): bool
    {
        $compressed = zlib_encode($original, $encoding);
        $decompressed = zlib_decode($compressed);

        return $decompressed === $original;
    }
}

$verifier = new RoundTripVerifier();
var_dump($verifier->verify('検証用のサンプルテキストです。'));
var_dump($verifier->verify('別の検証用テキスト', ZLIB_ENCODING_RAW));

例6:複数の圧縮形式が混在するデータを一括処理するツール

<?php

class MixedFormatBatchDecoder
{
    /**
     * gzip、zlib、raw deflateが混在する複数のデータを、
     * zlib_decode()の自動判別機能を活かして一括処理する
     */
    public function decodeAll(array $compressedItems): array
    {
        $results = [];

        foreach ($compressedItems as $key => $data) {
            $decoded = zlib_decode($data);
            $results[$key] = $decoded !== false ? $decoded : null;
        }

        return $results;
    }
}

$decoder = new MixedFormatBatchDecoder();
$items = [
    'gzip_item' => zlib_encode('gzipデータ', ZLIB_ENCODING_GZIP),
    'zlib_item' => zlib_encode('zlibデータ', ZLIB_ENCODING_DEFLATE),
    'raw_item'  => zlib_encode('rawデータ', ZLIB_ENCODING_RAW),
];
print_r($decoder->decodeAll($items));

例7:解凍失敗時のフォールバック処理を含む堅牢な実装

<?php

class FallbackDecoder
{
    /**
     * 解凍に失敗した場合、元のデータが
     * 圧縮されていなかった可能性を考慮してそのまま返す
     */
    public function decodeOrPassthrough(string $data): string
    {
        $result = @zlib_decode($data);

        // 解凍に失敗した場合、圧縮されていない生データの可能性がある
        return $result !== false ? $result : $data;
    }
}

$decoder = new FallbackDecoder();
$compressed = zlib_encode('圧縮されたデータ', ZLIB_ENCODING_GZIP);
$plain = '圧縮されていない普通の文字列';

echo $decoder->decodeOrPassthrough($compressed) . PHP_EOL;
echo $decoder->decodeOrPassthrough($plain) . PHP_EOL;

関連関数との比較

関数役割zlib_decodeとの違い
zlib_decode()圧縮形式を自動判別して解凍本記事の対象。gzip/zlib/raw deflateすべてに対応
zlib_encode()データを指定形式で圧縮zlib_decode()の対になるエンコード関数
gzuncompress()zlib形式専用の解凍形式がzlibであることが既知の場合に使う、より限定的な関数
gzdecode()gzip形式専用の解凍形式がgzipであることが既知の場合に使う、より限定的な関数
gzinflate()raw deflate形式専用の解凍ヘッダーなしのraw deflate形式専用(形式が既知の場合向け)

よくある落とし穴(注意点)

  1. 解凍後のデータサイズに上限を設けないと危険な場合がある 信頼できない外部ソースから受け取った圧縮データをそのまま解凍すると、非常に小さい圧縮データが巨大な展開後データになる「zip爆弾」のような攻撃によって、メモリを枯渇させられるリスクがあります。第2引数の $max_length を活用し、想定される最大サイズを指定しておくことが重要です(例3を参照)。
  2. 失敗時のfalseと、値としての空文字列を混同しない 解凍に失敗すると false が返りますが、厳密な比較(=== false)を行わないと、空文字列などの正当な結果と区別できない場合があります。
  3. 圧縮されていないデータをそのまま渡すとエラーになる zlib_decode() に非圧縮のプレーンなデータを渡すと解凍に失敗します。データが圧縮されているかどうかが不明な場合は、事前の判定やフォールバック処理を組み込むと安全です(例7を参照)。
  4. 形式限定の関数(gzuncompress()など)との使い分けを意識する 相手システムの圧縮形式が明確に分かっている場合、あえて自動判別を行う zlib_decode() を使わず、形式を限定した専用関数を使う方が、意図しない形式のデータを誤って処理してしまうリスクを避けられる場合があります。
  5. 拡張が無効化されている環境も稀にある 多くの環境で標準的に有効なzlib拡張ですが、最小構成でビルドされたPHP環境などでは無効化されている可能性もゼロではありません。念のため function_exists('zlib_decode') で確認しておくと安全です。

まとめ

観点まとめ
何をする関数かgzip・zlib・raw deflate形式の圧縮データを自動判別して解凍する
主な用途外部APIからの圧縮レスポンスの解凍、圧縮保存されたデータの読み出し
最大の特徴圧縮形式を意識せず、1つの関数で複数形式に対応できる
対になる関数zlib_encode()(圧縮)
注意点解凍後サイズの上限設定(zip爆弾対策)、失敗時のfalse判定、非圧縮データへの対処

zlib_decode() は、圧縮形式を意識せずにデータを解凍できる、利便性の高い関数です。ただし、信頼できない外部データを扱う際は、解凍後のサイズ上限を必ず設定するなど、リソース枯渇攻撃への配慮を忘れずに実装することが重要です。

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