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() и защищаться от ненадёжных имён записей при извлечении.