W3docs

PHP Zip

ZIP-архивы — это файлы, сжатые алгоритмом zip. Они используются для уменьшения размера файлов и упрощения их передачи.

ZIP-архив — это файл, объединяющий один или несколько файлов и сжимающий их с помощью алгоритма zip. Архивы широко применяются для уменьшения размера загружаемых файлов, группировки связанных файлов в одну распространяемую единицу и резервного копирования данных. Zip-архив обычно имеет расширение .zip.

В этой главе показано, как создавать, читать и извлекать ZIP-архивы в современном PHP с помощью встроенного класса ZipArchive, с запускаемыми примерами и описанием типичных подводных камней.

Класс ZipArchive

Устаревшие процедурные функции zip_* были объявлены устаревшими в PHP 7.4 и удалены в PHP 8.0. В современном коде следует использовать объектно-ориентированный класс ZipArchive, входящий в состав встроенного расширения zip (включите ext-zip, если оно ещё не активно — проверьте с помощью extension_loaded('zip')).

Наиболее часто используемые методы:

МетодНазначение
open($filename, $flags)Открывает архив для чтения или записи. Возвращает true или код ошибки.
addFile($path, $entryName)Добавляет файл с диска в архив.
addFromString($entryName, $contents)Добавляет запись из строки в памяти.
addEmptyDir($dirName)Добавляет запись пустой директории.
extractTo($directory, $entries)Извлекает все записи (или выбранное подмножество) в директорию.
getFromName($entryName)Читает одну запись в строку без обращения к диску.
statIndex($i) / numFilesИнспектирует записи и подсчитывает их количество.
getStatusString()Возвращает удобочитаемое сообщение о статусе для обработки ошибок.
close()Записывает ожидающие изменения и закрывает дескриптор.

Важно: Изменения, внесённые с помощью addFile() или addFromString(), записываются на диск только при вызове close(). Если забыть вызвать close(), архив окажется пустым или повреждённым.

Создание ZIP-архива

Передайте флаг ZipArchive::CREATE для создания нового архива, и добавьте ZipArchive::OVERWRITE, чтобы начать с чистого листа, если файл с таким именем уже существует:

$zip = new ZipArchive();
$zipName = 'documents.zip';

if ($zip->open($zipName, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
    // Add an entry from a string (no temp file needed).
    $zip->addFromString('readme.txt', "Hello from PHP!\n");

    // Add a file from disk, optionally under a folder inside the archive.
    $zip->addFile('report.csv', 'data/report.csv');

    // Add an empty directory entry.
    $zip->addEmptyDir('logs');

    echo "Adding {$zip->numFiles} entries to {$zipName}.\n";
    $zip->close(); // Must be called for the archive to be written.
    echo "Archive saved.\n";
} else {
    echo "Could not create the archive.\n";
}

При условии, что файл report.csv существует, будет выведено:

Adding 3 entries to documents.zip.
Archive saved.

Читайте numFiles до вызова close() — после закрытия дескриптора объект больше не отражает количество записей.

Чтение записей без извлечения

Вы можете просматривать содержимое архива или загружать отдельную запись в память, не распаковывая всё на диск:

$zip = new ZipArchive();

if ($zip->open('documents.zip') === true) {
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $entry = $zip->statIndex($i);
        echo $entry['name'] . ' (' . $entry['size'] . " bytes)\n";
    }

    // Read one entry straight into a string.
    echo "---\n" . $zip->getFromName('readme.txt');
    $zip->close();
}

Вывод:

readme.txt (16 bytes)
data/report.csv (20 bytes)
logs/ (0 bytes)
---
Hello from PHP!

Извлечение архива на диск

extractTo() распаковывает архив. Всегда проверяйте возвращаемое значение и сообщайте об ошибках с помощью getStatusString():

$zip = new ZipArchive();
$filename = 'documents.zip';
$extractTo = './extracted_files';

if ($zip->open($filename) === true) {
    if (!is_dir($extractTo)) {
        mkdir($extractTo, 0755, true);
    }

    if ($zip->extractTo($extractTo) === true) {
        echo "Archive extracted successfully.";
    } else {
        echo "Extraction failed: " . $zip->getStatusString();
    }

    $zip->close();
} else {
    echo "Failed to open archive.";
}

Чтобы извлечь только определённые файлы, передайте их имена вторым аргументом:

$zip->extractTo($extractTo, ['readme.txt', 'data/report.csv']);

Типичные подводные камни

  • Всегда вызывайте close(). До этого момента добавления хранятся только в памяти, а файл на диске может оставаться пустым.
  • open() не всегда возвращает false при ошибке. При сбое он возвращает целочисленный код ошибки (например, ZipArchive::ER_NOENT, если файл не найден). Строгое сравнение через === true — надёжный способ обнаружить успех.
  • Zip Slip. При извлечении ненадёжных архивов вредоносная запись с именем вида ../../etc/passwd может выйти за пределы целевой директории. Проверяйте или очищайте имена записей перед извлечением файлов, которые вы не создавали.
  • Память. getFromName() загружает всю запись в память; для больших записей предпочтительнее использовать extractTo() или потоковое чтение через getStream().

Связанные темы

  • PHP file_put_contents() — запись данных, прочитанных из архива, обратно на диск.
  • PHP file_get_contents() — чтение целых файлов, например, перед добавлением их в zip.
  • PHP file_exists() — проверка наличия файла перед вызовом addFile().
  • PHP fread() — чтение содержимого файла по частям.

Заключение

Класс ZipArchive — это современный и надёжный способ работы с ZIP-архивами в PHP. Вы можете создавать архивы с помощью addFile() и addFromString(), просматривать их с помощью numFiles и statIndex(), загружать отдельные записи в память с помощью getFromName() и распаковывать с помощью extractTo() — не забывая всегда вызывать close() и защищаться от ненадёжных имён записей при извлечении.

Практика

Практика
Какие из следующих утверждений верны относительно расширения ZIP в PHP?
Какие из следующих утверждений верны относительно расширения ZIP в PHP?
Was this page helpful?