圧縮フィルタ

圧縮ストリーム を用いれば ローカルファイルシステム上に gzip や bz2 と互換性のある圧縮ファイルを 作成することができます。しかし、これはネットワーク越しの圧縮機能を 持っておらず、また非圧縮ストリームを圧縮されたストリームに変換することも できません。その点、圧縮フィルタはどんなストリームリソースでもどんな場合 でも適用可能です。

注意:

圧縮フィルタは、gzip のようなコマンドライン ユーティリティで使われるヘッダを生成しません。 これらのフィルタは、(ヘッダ部を除いた)データストリームの本体のみを 圧縮・展開するものです。

zlib.deflate と zlib.inflate

zlib.deflate (圧縮) と zlib.inflate (展開) は、 » RFC 1951 で述べられている圧縮方法を 実装したものです。deflate フィルタには、次の 3 つの パラメータを連想配列形式で渡すことができます: level は、圧縮の度合いを 1から9 までで表した ものです。数字が大きいほど圧縮後のサイズが小さくなりますが、そのぶん 処理時間が長くかかります。次の2つの値は特別な意味を持ちます。 0 (一切圧縮しない)、そして -1 (zlib のデフォルト設定 -- 現在は 6)。 window は、圧縮用ループバックウィンドウのサイズを (2 を底とする)対数で指定します。大きな値 (15 -- つまり 32768 バイトまで 引き上げる) を指定するとメモリをふんだんに利用してより小さく圧縮されます。 一方、小さな値 (9 -- つまり 512 バイトまで絞り込む) を指定すると、圧縮の 効率は落ちますがメモリの消費量を抑えられます。値を指定しなかった際の window の初期値は、現在 15 です。 zlib.deflate フィルタは DEFLATE、ZLIB、GZIP の 3 つの圧縮方法を実装しており、どれを使うかは window パラメータの値で決まります。 window パラメータの下位 4 ビットは、内部で使われる「ヒストリバッファ」の サイズを、2 を底とする対数で指定するもので、8 から 15 までの 範囲を取ります。それ以外のビットの意味は、 以下のとおりです。

  • DEFLATE (» RFC 1951) は、ヘッダもチェックサムも持たない生の圧縮アルゴリズムです。 window パラメータに -9 から -15 の範囲の値を指定した場合に、この圧縮が行われます。 この圧縮アルゴリズムは、zlib.deflate フィルタが生成する すべてのフォーマットの基礎になっています。 文字列を直接扱う関数としては、gzdeflate() と gzinflate() が対応します。

  • ZLIB (» RFC 1950) は、DEFLATE アルゴリズムを適用した上で、2 バイトのヘッダと、 圧縮前のデータの Adler32 チェックサムをビッグエンディアンのバイトオーダーで 格納した 4 バイトのトレーラーを追加します:

    ZLIB = ZLIBHEADER(2B)  DEFLATE  ADLER32(4B)
    2 バイトのヘッダをビッグエンディアンの 16 ビット符号なし整数として読んだ値は、 31 の倍数でなければなりません。 window パラメータに 8 から 15 の範囲の値を指定した場合に、 このフォーマットが生成されます。 文字列を直接扱う関数としては、gzcompress() と gzuncompress() が対応します。

  • GZIP (» RFC 1952) は、DEFLATE アルゴリズムを適用した上で、ヘッダと、 圧縮前のデータの CRC32 チェックサムおよびその長さを、 どちらもリトルエンディアンのバイトオーダーで格納したトレーラーを追加します:

    GZIP = GZIPHEADER(10B)  DEFLATE  CRC32(4B)  LENGTH(4B)
    これが .gz ファイルのフォーマットです。 GZIPHEADER には、順に GZIP シグネチャ (\x1f\x8B)、 圧縮メソッド (\x08)、値がゼロのフラグバイト、 値がゼロの 4 バイトの更新時刻、圧縮レベルに応じた extra-flags バイト、 そして現在のオペレーティングシステム (\x00 = FAT ファイルシステム、 \x03 = Unix など) が格納されます。 window パラメータに 9+16=25 から 15+16=31 の範囲の値を指定した場合に、 このフォーマットが生成されます。 圧縮前のデータの長さには 4GB という上限があることに注意してください。 この上限を超えた場合、LENGTH の部分には実際の長さを 2^32 で割った余りだけが格納されます。 文字列を直接扱う関数としては、gzencode() と gzdecode() が対応します。また gzopen() 関数を使うと、.gz ファイルの読み書きができます。

zlib.inflate フィルタで指定できるのは window パラメータだけで、それ以外のパラメータ (memory、level) は無視されます。 $W をヒストリバッファのサイズの 2 を底とする対数とすると、展開時には 2^$W バイトが確保されます。ZLIB フォーマットの場合、この値はヘッダに記録された 値以上でなければならず、展開時にチェックされます。それ以外のフォーマットの場合は、 データ中に実際に現れるマッチ距離をまかなえる大きさであれば十分です。 範囲は 9 ≤ $W ≤ 15 です。値がわからない場合は $W=15 にしておくのが安全です。
  • DEFLATE (» RFC 1951): window=-$W を指定します。$W には、圧縮時に使った値以上を指定します。 値がわからない場合は window=-15 を指定してください。

  • ZLIB (» RFC 1950): window=$W を指定します。$W の値は ZLIB ヘッダから取得できます。 値がわからない場合は window=15 を指定してください。

  • GZIP (» RFC 1952): window=$W+16 を指定します。GZIP ヘッダは window サイズを保持していないので、 圧縮時に使った値がわからない場合は window=31 を指定してください。

  • ZLIB or GZIP: ヘッダを自動で検出させるには window=$W+32 を指定します。こうすると どちらのフォーマットも認識して展開できます。 window=15+32=47 にしておくのが安全です。

memory は、作業用の一時メモリをどの程度割り当てるかを 指定します。1 (最小限) から 9 (最大限) の間で指定できます。この値は 圧縮の速度のみに影響し、圧縮後のデータのサイズには影響しません。

注意:

level は一番よく使われるパラメータなので、このパラメータについては (配列形式ではなく)直接整数値として設定することも可能にしています。

zlib サポートが有効な場合、 zlib.* 圧縮フィルタが利用可能になります。

例1 zlib.deflate と zlib.inflate

<?php
$params = array('level' => 6, 'window' => 15, 'memory' => 9);

$original_text = "This is a test.\nThis is only a test.\nThis is not an important string.\n";
echo "もとのテキストの長さは " . strlen($original_text) . " 文字です。\n";

$fp = fopen('test.deflated', 'w');
stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
fwrite($fp, $original_text);
fclose($fp);

echo "圧縮後のファイルの大きさは " . filesize('test.deflated') . " バイトです。\n";
echo "もとのテキストは:\n";
/* readfile と zlib.inflate を利用し、メモリ上で展開します */
readfile('php://filter/zlib.inflate/resource=test.deflated');

/* 生成される出力:

もとのテキストの長さは 70 文字です。
圧縮後のファイルの大きさは 56 バイトです。
もとのテキストは:
This is a test.
This is only a test.
This is not an important string.

 */
?>

例2 zlib.deflate のシンプルな例

<?php
$original_text = "This is a test.\nThis is only a test.\nThis is not an important string.\n";
echo "もとのテキストの長さは " . strlen($original_text) . " 文字です。\n";

$fp = fopen('test.deflated', 'w');
/* この "6" は、パラメータ "level" が 6 であるということ */
stream_filter_append($fp, 'zlib.deflate', STREAM_FILTER_WRITE, 6);
fwrite($fp, $original_text);
fclose($fp);

echo "圧縮後のファイルの大きさは " . filesize('test.deflated') . " バイトです。\n";

/* 生成される出力:

もとのテキストの長さは 70 文字です。
圧縮後のファイルの大きさは 56 バイトです。

 */
?>

bzip2.compress と bzip2.decompress

bzip2.compress と bzip2.decompress は、上で示した zlib フィルタと同じような動作をします。 bzip2.compress フィルタには、次の 2 つの パラメータを連想配列形式で渡すことができます: blocks は 1 から 9 までの整数値を設定します。 これは一時領域として割り当てるメモリのサイズを 100kバイトブロックの 数で示したものです。 work も整数値で、0 から 250 までの値を設定します。 これは、通常の圧縮方法がうまくいかなかった際に、どのくらい再試行した後で 速度の遅い(ただしより確実な)方法に切りかえるかを示します。 このパラメータは圧縮の速度のみに影響します。圧縮後のデータのサイズや 圧縮時のメモリ使用量は変わりません。0 を指定した場合は、bzip ライブラリの 初期設定値が利用されます。 bzip2.decompress フィルタには 1 つのパラメータを 渡すことができます。このパラメータは普通に論理型の値として渡すことも できますし、連想配列形式で small という名前の 要素として渡すこともできます。 small を true に設定した場合、bzip ライブラリは メモリ使用量をできるだけ抑えるようになり、その分、処理速度は遅くなります。

bz2 サポートが有効な場合、 bzip2.* 圧縮フィルタが利用可能になります。

例3 bzip2.compress と bzip2.decompress

<?php
$param = array('blocks' => 9, 'work' => 0);

echo "もとのファイルの大きさは " . filesize('LICENSE') . " バイトです。\n";

$fp = fopen('LICENSE.compressed', 'w');
stream_filter_append($fp, 'bzip2.compress', STREAM_FILTER_WRITE, $param);
fwrite($fp, file_get_contents('LICENSE'));
fclose($fp);

echo "圧縮後のファイルの大きさは " . filesize('LICENSE.compressed') . " バイトです。\n";

/* 生成される出力:

もとのファイルの大きさは 3288 バイトです。
圧縮後のファイルの大きさは 1488 バイトです。

 */
?>
+add a note

User Contributed Notes 4 notes

up
8
Anonymous
11 years ago
To read a gzip encoded stream from http
<?php
$opts = [
    "http" => [
        "method" => "GET",
        "header" => [ "Accept-Encoding: gzip" ],
    ]
];
$ctx = stream_context_create($opts);
$f = fopen("http://php.net", "r", false, $ctx);
// check  stream_get_meta_data($f)["wrapper_data"] has "Content-Encoding: gzip"
stream_filter_append($f, "zlib.inflate", STREAM_FILTER_READ, ["window" => 30]);
echo stream_get_contents($f); // any stream processing
fclose($f);
up
2
bohwaz
8 years ago
Please note that there is currently a bug in this feature. ftell(), fseek() and fstat() functions cannot be used. Writing to a stream after using this function will not change the stream position as it should.

See bug: https://bugs.php.net/bug.php?id=49874

Also the zlib filters don't work with php://temp, php://memory and php://input streams, nothing is outputted to those streams.
up
0
TingSong
3 years ago
To decompress a gzipped stream:

<?php
$stream = fopen('https://example.com/some/file.txt.gz', 'rb');
stream_filter_append($stream, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15+16]);

// read the decompressed line directly
$line = fgets($stream);

// process the lines
?>

As the doc of zlib https://www.zlib.net/manual.html#Advanced

The 'window' parameter between 8 and 15 specified the window size from 2⁸ to 2¹⁵ bytes. It can be added by 16 for wrapping with gzip header and trailer instead of zlib wrapper.

And, window could be -8..-15 for unwrapping RAW deflate data.
up
0
Anonymous
5 years ago
To use the zlib.inflate filter with data originally written using gzcompress() or zlib.deflate, set the window option to 15 as outlined here: https://bugs.php.net/bug.php?id=68556

<?php
$fh = fopen(file_name, 'rb');
stream_filter_append($fh, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15]);
$contents = stream_get_contents($fh);
fclose($fh);