[PHP]zip_entry_filesizeとは?非推奨のZIPエントリ元サイズ取得関数とZipArchiveへの移行を徹底解説

PHP

はじめに

これまでの記事で、非推奨の手続き型ZIP関数群から zip_close()、zip_entry_close()、zip_entry_compressedsize()、zip_entry_compressionmethod() を解説してきました。今回取り上げる zip_entry_filesize() も同じ関数群に属し、ZIPアーカイブ内の各エントリの圧縮前の元のファイルサイズを取得するための関数です。

zip_entry_compressedsize() の記事で触れた通り、ZIPエントリには「圧縮前のサイズ」と「圧縮後のサイズ」という2つのサイズ情報があります。zip_entry_filesize() は前者、つまり展開したときに元のファイルが持つ本来のサイズを返します。この値は、エントリの内容を読み込む際に zip_entry_read() に渡す読み込みバイト数としても使われていました。前回までと同様、この関数もPHP 7.2.0以降で非推奨とされています。本記事では、その役割と ZipArchive を使った現代的な代替実装を詳しく解説します。


関数概要

項目内容
関数名zip_entry_filesize()
所属拡張Zip拡張(手続き型インターフェース)
シグネチャzip_entry_filesize(resource $zip_entry): int
引数$zip_entry — zip_read() などで取得したエントリのリソース
戻り値エントリの圧縮前(展開後)のバイト数
対応バージョンPHP 4.1.0以降。PHP 7.2.0以降は非推奨
対になる情報zip_entry_compressedsize()(圧縮後のサイズ)
推奨される代替ZipArchive::statIndex() / statName() の 'size' フィールド

元サイズの位置づけ(イメージ図)

  ZIPアーカイブ内のエントリ "report.csv"
              │
   ┌──────────┴──────────────────┐
   ▼                                ▼
  圧縮前のサイズ(元サイズ)         圧縮後のサイズ
  zip_entry_filesize()             zip_entry_compressedsize()
  ★この記事の対象                   (前々回の記事)
  例: 50,000 バイト                 例: 8,500 バイト
   │
   ▼
  【主な用途】
  ・展開後に必要なディスク容量の把握
  ・zip_entry_read()に渡す読み込みバイト数の指定
    zip_entry_read($entry, zip_entry_filesize($entry))

ポイントは、古い手続き型インターフェースにおいて、zip_entry_filesize() が単なる情報取得にとどまらず、エントリ内容を最後まで読み込むための引数としても使われていたという点です。ZipArchive::getFromIndex() のようなメソッドでは、サイズを明示的に指定しなくてもエントリ全体を取得できるため、この用途自体が不要になります。


実践サンプル7選

例1:非推奨の古い関数を使った基本的な使い方(参考・非推奨)

<?php

class LegacyFileSizeReader
{
    /**
     * 注意: PHP 7.2.0以降、この一連の関数は非推奨です
     * 新規開発ではZipArchiveの使用を強く推奨します
     */
    public function getFileSizes(string $zipPath): array
    {
        $sizes = [];
        $zip = zip_open($zipPath);

        if (is_resource($zip)) {
            while ($entry = zip_read($zip)) {
                $sizes[zip_entry_name($entry)] = zip_entry_filesize($entry);
            }
            zip_close($zip);
        }

        return $sizes;
    }
}

$reader = new LegacyFileSizeReader();
print_r($reader->getFileSizes('/tmp/sample.zip'));

例2:ZipArchiveを使った推奨の代替実装

<?php

class ModernFileSizeReader
{
    /**
     * ZipArchive::statIndex()が返す配列の
     * 'size'キーで圧縮前のサイズを取得できる
     */
    public function getFileSizes(string $zipPath): array
    {
        $sizes = [];
        $zip = new ZipArchive();

        if ($zip->open($zipPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $stat = $zip->statIndex($i);
                $sizes[$stat['name']] = $stat['size'];
            }
            $zip->close();
        }

        return $sizes;
    }
}

$reader = new ModernFileSizeReader();
print_r($reader->getFileSizes('/tmp/sample.zip'));

例3:古い読み込みパターンとZipArchiveの比較

<?php

class ReadPatternComparison
{
    /**
     * 古い方式では、読み込むバイト数として
     * zip_entry_filesize()の値を明示的に渡す必要があった
     */
    public function legacyRead(string $zipPath, string $targetName): ?string
    {
        $zip = zip_open($zipPath);
        if (!is_resource($zip)) {
            return null;
        }

        $content = null;
        while ($entry = zip_read($zip)) {
            if (zip_entry_name($entry) === $targetName) {
                zip_entry_open($zip, $entry);
                // サイズを指定しないと全体を読み込めない
                $content = zip_entry_read($entry, zip_entry_filesize($entry));
                zip_entry_close($entry);
                break;
            }
        }
        zip_close($zip);

        return $content;
    }

    /**
     * ZipArchiveではサイズ指定なしでエントリ全体を取得できる
     */
    public function modernRead(string $zipPath, string $targetName): ?string
    {
        $zip = new ZipArchive();
        if ($zip->open($zipPath) !== true) {
            return null;
        }

        $content = $zip->getFromName($targetName);
        $zip->close();

        return $content !== false ? $content : null;
    }
}

例4:サイズ上限を超えるエントリを事前に検出する安全チェッククラス

<?php

class OversizedEntryDetector
{
    /**
     * 展開前に各エントリの元サイズを確認し、
     * 極端に大きなファイル(zip爆弾の可能性)を検出する
     */
    public function detectOversized(string $zipPath, int $maxBytes): array
    {
        $oversized = [];
        $zip = new ZipArchive();

        if ($zip->open($zipPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $stat = $zip->statIndex($i);
                if ($stat['size'] > $maxBytes) {
                    $oversized[] = [
                        'name' => $stat['name'],
                        'size' => $stat['size'],
                    ];
                }
            }
            $zip->close();
        }

        return $oversized;
    }
}

$detector = new OversizedEntryDetector();
print_r($detector->detectOversized('/tmp/sample.zip', 10 * 1024 * 1024));

例5:アーカイブの内容一覧をサイズ付きで表示するクラス

<?php

class ArchiveListingFormatter
{
    /**
     * unzip -l コマンドのような形式で、
     * エントリ名とサイズの一覧を整形表示する
     */
    public function formatListing(string $zipPath): string
    {
        $lines = [];
        $zip = new ZipArchive();

        if ($zip->open($zipPath) === true) {
            $lines[] = sprintf('%10s  %s', 'Size', 'Name');
            $lines[] = str_repeat('-', 50);

            for ($i = 0; $i < $zip->numFiles; $i++) {
                $stat = $zip->statIndex($i);
                $lines[] = sprintf('%10d  %s', $stat['size'], $stat['name']);
            }

            $zip->close();
        }

        return implode("\n", $lines);
    }
}

$formatter = new ArchiveListingFormatter();
echo $formatter->formatListing('/tmp/sample.zip') . PHP_EOL;

例6:サイズが0のエントリ(ディレクトリ等)を除外するフィルター

<?php

class NonEmptyEntryFilter
{
    /**
     * サイズ0のエントリ(ディレクトリや空ファイル)を除外し、
     * 実体のあるファイルのみを抽出する
     */
    public function filterRealFiles(string $zipPath): array
    {
        $files = [];
        $zip = new ZipArchive();

        if ($zip->open($zipPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $stat = $zip->statIndex($i);

                // ディレクトリエントリは名前が"/"で終わり、サイズが0であることが多い
                $isDirectory = str_ends_with($stat['name'], '/');
                if (!$isDirectory && $stat['size'] > 0) {
                    $files[$stat['name']] = $stat['size'];
                }
            }
            $zip->close();
        }

        return $files;
    }
}

$filter = new NonEmptyEntryFilter();
print_r($filter->filterRealFiles('/tmp/sample.zip'));

例7:人間が読みやすい単位にサイズを変換するユーティリティ

<?php

class HumanReadableSizeFormatter
{
    /**
     * バイト数を KB / MB / GB などの
     * 読みやすい単位に変換して表示する
     */
    public function formatSizes(string $zipPath): array
    {
        $formatted = [];
        $zip = new ZipArchive();

        if ($zip->open($zipPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $stat = $zip->statIndex($i);
                $formatted[$stat['name']] = $this->toHumanReadable($stat['size']);
            }
            $zip->close();
        }

        return $formatted;
    }

    private function toHumanReadable(int $bytes): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $index = 0;
        $size = (float) $bytes;

        while ($size >= 1024 && $index < count($units) - 1) {
            $size /= 1024;
            $index++;
        }

        return round($size, 2) . ' ' . $units[$index];
    }
}

$formatter = new HumanReadableSizeFormatter();
print_r($formatter->formatSizes('/tmp/sample.zip'));

関連関数との比較

関数/メソッド役割zip_entry_filesizeとの違い
zip_entry_filesize()手続き型でエントリの圧縮前サイズを取得本記事の対象。PHP 7.2.0以降は非推奨
zip_entry_compressedsize()手続き型でエントリの圧縮後サイズを取得対象が「圧縮後」のサイズである点が異なる
ZipArchive::statIndex()インデックス指定でエントリの統計情報を取得size(圧縮前)とcomp_size(圧縮後)を同時に取得できる
ZipArchive::getFromIndex()エントリの内容を取得サイズを指定しなくてもエントリ全体を取得できる
filesize()通常のファイルのサイズを取得ZIPアーカイブ内のエントリではなく、ファイルシステム上のファイルが対象

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

  1. PHP 7.2.0以降でE_DEPRECATED警告が発生する これまでの記事と同様、この関数群を使用すると非推奨警告が発生します。早期の ZipArchive への移行が推奨されます。
  2. ZipArchiveでは読み込み時にサイズ指定が不要になる 古い方式では zip_entry_read($entry, zip_entry_filesize($entry)) のようにサイズを明示的に渡す必要がありましたが、ZipArchive::getFromName() や getFromIndex() はエントリ全体を自動的に取得します。移行時にこの引数の扱いが不要になる点を理解しておきましょう(例3を参照)。
  3. 展開前のサイズ確認はセキュリティ上重要 非常に小さなZIPファイルが展開すると巨大なデータになる「zip爆弾」への対策として、展開前に各エントリの元サイズを確認することは有効な防御手段になります(例4を参照)。
  4. ディレクトリエントリのサイズは0になる ZIPアーカイブ内のディレクトリを表すエントリは、通常サイズが0バイトです。ファイル数のカウントや合計サイズの算出を行う際は、これらを適切に除外する処理を検討しましょう(例6を参照)。
  5. 圧縮前サイズと実際の展開後ディスク使用量は完全には一致しない ファイルシステムのブロックサイズの都合上、実際にディスク上で占める容量は、size が示す値よりわずかに大きくなることがあります。厳密な容量管理が必要な場合は、この点も考慮しましょう。

まとめ

観点まとめ
何をする関数かZIPエントリの圧縮前(展開後)のバイト数を取得する(手続き型インターフェース)
現在の状態PHP 7.2.0以降で非推奨。将来的に削除される可能性がある
推奨される代替ZipArchive::statIndex() / statName() の size フィールド
古い方式での特殊な用途zip_entry_read()に渡す読み込みバイト数としても使われていた
注意点展開前のサイズ確認によるzip爆弾対策、ディレクトリエントリの0バイト扱い

zip_entry_filesize() は、ZIPエントリの本来のサイズを知るための、非推奨の手続き型関数です。ZipArchive::statIndex() へ移行することで、圧縮前後のサイズを含む複数の情報を一度に取得でき、さらに読み込み時のサイズ指定という煩雑さからも解放されます。

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