[PHP]zip_entry_closeとは?非推奨のZIPエントリリソース解放関数とZipArchiveへの移行を徹底解説

PHP

はじめに

前回の記事では、非推奨の手続き型ZIP関数群の中から、アーカイブ全体を閉じる zip_close() を解説しました。今回取り上げる zip_entry_close() は、同じ非推奨の関数群に属しますが、対象が「アーカイブ全体」ではなく**個々のZIPエントリ(ファイル)**である点が異なります。

zip_entry_open() を使ってZIPアーカイブ内の特定のファイルを読み込み用に開いた場合、そのエントリのリソースを解放するのが zip_entry_close() の役割です。前回の記事と同様、この関数もPHP 7.2.0以降で非推奨とされており、現在は ZipArchive クラスの利用が強く推奨されています。本記事では、この関数の役割と、ZipArchive を使った現代的な代替実装を詳しく解説します。


関数概要

項目内容
関数名zip_entry_close()
所属拡張Zip拡張(手続き型インターフェース)
シグネチャzip_entry_close(resource $zip_entry): bool
引数$zip_entry — zip_entry_open() で取得したエントリのリソース
戻り値成功時に true
対応バージョンPHP 4.1.0以降。PHP 7.2.0以降は非推奨
対になる関数zip_entry_open()(エントリを開く)
推奨される代替ZipArchive::getStream() や ZipArchive::getFromName()

エントリ単位のリソース管理(イメージ図)

  【古い手続き型インターフェース(非推奨)】
  zip_open($path)                      ← アーカイブ全体を開く
        │
        ▼
  zip_read($zip)                       ← エントリを1つ取得
        │
        ▼
  zip_entry_open($zip, $entry)         ← そのエントリを個別に「開く」
        │
        ▼
  zip_entry_read($entry)               ← エントリの中身を読み込む
        │
        ▼
  zip_entry_close($entry)              ← ★この記事の対象。エントリを閉じる
        │
        ▼
  zip_close($zip)                      ← アーカイブ全体を閉じる(前回記事)


  【現在推奨されるオブジェクト指向インターフェース】
  $zip = new ZipArchive();
  $zip->open($path);
  $content = $zip->getFromName($entryName);  ← 1回の呼び出しで完結
  $zip->close();

ポイントは、古いインターフェースでは**「アーカイブを開く」「エントリを開く」という2段階のリソース管理が必要だった一方、ZipArchive の getFromName() のようなメソッドを使えば、この個別のエントリ管理が内部で自動的に処理される**という点です。これにより、開発者が明示的に zip_entry_close() を呼び出す必要がなくなり、コードの記述量も大幅に減ります。


実践サンプル7選

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

<?php

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

        if (is_resource($zip)) {
            while ($entry = zip_read($zip)) {
                if (zip_entry_open($zip, $entry)) {
                    $name = zip_entry_name($entry);
                    $size = zip_entry_filesize($entry);
                    $contents[$name] = zip_entry_read($entry, $size);

                    // 各エントリを個別に閉じる必要がある
                    zip_entry_close($entry);
                }
            }
            zip_close($zip);
        }

        return $contents;
    }
}

$reader = new LegacyEntryReader();
print_r(array_keys($reader->readAllContents('/tmp/sample.zip')));

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

<?php

class ModernEntryReader
{
    /**
     * ZipArchiveでは、getFromName()やgetFromIndex()を使うことで
     * エントリの個別クローズを意識せずに内容を取得できる
     */
    public function readAllContents(string $zipPath): array
    {
        $contents = [];
        $zip = new ZipArchive();

        if ($zip->open($zipPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $name = $zip->getNameIndex($i);
                $contents[$name] = $zip->getFromIndex($i);
            }
            $zip->close();
        }

        return $contents;
    }
}

$reader = new ModernEntryReader();
print_r(array_keys($reader->readAllContents('/tmp/sample.zip')));

例3:大きなファイルをストリームとして扱うモダンな実装

<?php

class StreamingEntryReader
{
    /**
     * 大きなエントリをメモリに一度に読み込むのではなく、
     * ZipArchive::getStream()でストリームとして扱う
     * (古いzip_entry_read()の逐次読み込みに近い性質)
     */
    public function streamEntry(string $zipPath, string $entryName): ?string
    {
        $zip = new ZipArchive();

        if ($zip->open($zipPath) !== true) {
            return null;
        }

        $stream = $zip->getStream($entryName);
        if ($stream === false) {
            $zip->close();
            return null;
        }

        $buffer = '';
        while (!feof($stream)) {
            $buffer .= fread($stream, 8192);
        }
        fclose($stream);
        $zip->close();

        return $buffer;
    }
}

$reader = new StreamingEntryReader();
echo $reader->streamEntry('/tmp/sample.zip', 'large_file.txt') . PHP_EOL;

例4:例外安全性を考慮したリソース管理のパターン

<?php

class ExceptionSafeZipReader
{
    /**
     * try-finallyパターンで、
     * 処理中に例外が発生してもZipArchiveが確実に閉じられるようにする
     * (古いzip_entry_close()の呼び忘れリスクを解消する設計)
     */
    public function processEntries(string $zipPath, callable $processor): array
    {
        $zip = new ZipArchive();
        $results = [];

        if ($zip->open($zipPath) !== true) {
            throw new RuntimeException('ZIPファイルを開けませんでした');
        }

        try {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $name = $zip->getNameIndex($i);
                $content = $zip->getFromIndex($i);
                $results[$name] = $processor($name, $content);
            }
        } finally {
            $zip->close();
        }

        return $results;
    }
}

$reader = new ExceptionSafeZipReader();
print_r($reader->processEntries('/tmp/sample.zip', fn ($name, $content) => strlen($content)));

例5:非推奨関数と現代的手法のパフォーマンス比較デモ

<?php

class PerformanceComparisonDemo
{
    /**
     * 手続き型とZipArchiveのコード量・複雑さを比較するための
     * 教育目的のデモ実装
     */
    public function measureLegacyApproach(string $zipPath): float
    {
        $start = microtime(true);
        $zip = zip_open($zipPath);
        if (is_resource($zip)) {
            while ($entry = zip_read($zip)) {
                zip_entry_open($zip, $entry);
                zip_entry_read($entry, zip_entry_filesize($entry));
                zip_entry_close($entry);
            }
            zip_close($zip);
        }
        return microtime(true) - $start;
    }

    public function measureModernApproach(string $zipPath): float
    {
        $start = microtime(true);
        $zip = new ZipArchive();
        if ($zip->open($zipPath) === true) {
            for ($i = 0; $i < $zip->numFiles; $i++) {
                $zip->getFromIndex($i);
            }
            $zip->close();
        }
        return microtime(true) - $start;
    }
}

// $demo = new PerformanceComparisonDemo();
// echo "Legacy: " . $demo->measureLegacyApproach('/tmp/sample.zip') . "s\n";
// echo "Modern: " . $demo->measureModernApproach('/tmp/sample.zip') . "s\n";

例6:既存コードのエントリ単位のクローズ漏れを検出するツール

<?php

class EntryCloseLeakDetector
{
    /**
     * zip_entry_open()の呼び出しに対して、
     * 対応するzip_entry_close()が漏れていないかを
     * 静的解析的に簡易チェックする
     */
    public function detectLeaks(string $phpFilePath): array
    {
        $content = file_get_contents($phpFilePath);

        $openCount = preg_match_all('/\bzip_entry_open\s*\(/', $content);
        $closeCount = preg_match_all('/\bzip_entry_close\s*\(/', $content);

        return [
            'open_calls'  => $openCount,
            'close_calls' => $closeCount,
            'potential_leak' => $openCount > $closeCount,
        ];
    }
}

$detector = new EntryCloseLeakDetector();
// print_r($detector->detectLeaks('/path/to/legacy_code.php'));

例7:非推奨警告を抑制しつつ段階的に移行するアダプタークラス

<?php

class ZipReaderAdapter
{
    /**
     * 既存のインターフェースを保ちながら、
     * 内部実装だけをZipArchiveベースに切り替えるアダプターパターン
     * (段階的な移行を行う際の実践的な手法)
     */
    public function __construct(private string $zipPath)
    {
    }

    public function getEntryContent(string $entryName): ?string
    {
        // 呼び出し側からはエントリのopen/closeを意識させず、
        // 内部でZipArchiveの開閉を完結させる
        $zip = new ZipArchive();
        if ($zip->open($this->zipPath) !== true) {
            return null;
        }

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

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

$adapter = new ZipReaderAdapter('/tmp/sample.zip');
echo $adapter->getEntryContent('readme.txt') . PHP_EOL;

関連関数との比較

関数/メソッド役割zip_entry_closeとの違い
zip_entry_close()手続き型インターフェースで個別エントリのリソースを解放本記事の対象。PHP 7.2.0以降は非推奨
zip_entry_open()手続き型インターフェースで個別エントリを開くzip_entry_close()と対になる、同じく非推奨の関数
zip_close()アーカイブ全体を閉じるエントリ単位ではなく、アーカイブ全体を対象とする(前回記事を参照)
ZipArchive::getFromName()名前を指定してエントリの内容を直接取得エントリのopen/close管理を内部で自動化している
ZipArchive::getStream()エントリをストリームとして取得大きなファイルを逐次読み込みたい場合に、明示的なfclose()で管理する

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

  1. PHP 7.2.0以降でE_DEPRECATED警告が発生する zip_close() の記事でも触れた通り、この関数群を使用すると非推奨警告がログに出力されます。早期の ZipArchive への移行が推奨されます。
  2. zip_entry_open() とzip_entry_close()の対応漏れによるリソースリーク 古いインターフェースでは、開いたエントリを確実に閉じる責任がすべて開発者側にありました。ループ処理の途中でエラーが発生した場合など、zip_entry_close() の呼び出しが漏れるリスクがあります(例6のような検出ツールが役立ちます)。
  3. ZipArchiveではエントリ単位のクローズを意識する必要がほぼない getFromName() や getFromIndex() のようなメソッドは、内部でエントリの開閉を完結させるため、古いインターフェースで必要だった煩雑なリソース管理から解放されます(例2を参照)。
  4. getStream()を使う場合はfclose()の呼び出しが必要 ストリームとして取得したエントリについては、通常のファイルストリームと同様に fclose() で明示的に閉じる必要があります。この点は、エントリのクローズという概念自体がなくなったわけではないことに注意しましょう(例3を参照)。
  5. 例外発生時のリソース解放を保証する設計が重要 古い手続き型インターフェースでは、処理中に例外が発生すると zip_entry_close() の呼び出しがスキップされるリスクがありました。ZipArchive を使う場合も、try...finally を活用して確実にリソースを解放する設計を心がけましょう(例4を参照)。

まとめ

観点まとめ
何をする関数かzip_entry_open()で開いた個別のZIPエントリのリソースを解放する(手続き型インターフェース)
現在の状態PHP 7.2.0以降で非推奨。将来的に削除される可能性がある
推奨される代替ZipArchive::getFromName(), getFromIndex()(内部でエントリ管理が自動化される)
管理の煩雑さ古い方式は「アーカイブ」と「エントリ」の2段階のリソース管理が必要だった
移行の必要性新規開発では使用を避け、既存コードは計画的にZipArchiveへ移行することが望ましい

zip_entry_close() は、アーカイブ全体とは別に、個々のエントリのリソース管理を担っていた、非推奨の手続き型関数です。ZipArchive クラスへ移行することで、このような細かなリソース管理から解放され、よりシンプルで安全なコードを書くことができます。

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