Расширение Zip для PHP: создание, извлечение и управление архивами
Работа с архивами через расширение Zip
Расширение php_zip предоставляет класс ZipArchive для создания, чтения и изменения ZIP-архивов. Оно основано на библиотеке libzip и поддерживает сжатие, шифрование (AES-256 при сборке с libzip 1.6+) и работу с потоками. Ниже рассмотрены основные сценарии использования.
Как создать ZIP-архив из нескольких файлов?
Создание архива с добавлением одного или нескольких файлов:
$zip = new ZipArchive();
$result = $zip->open('example.zip', ZipArchive::CREATE);
if ($result === true) {
$zip->addFile('/path/to/file1.txt', 'file1.txt');
$zip->addFile('/path/to/file2.jpg', 'images/file2.jpg');
$zip->close();
echo 'Архив создан';
}Php zip extension (расширение zip для php)
Второй параметр open() определяет режим: ZipArchive::CREATE создаёт новый архив или перезаписывает существующий. Третий аргумент метода addFile() - локальное имя внутри архива (можно с поддиректориями).
Типичная ошибка: неверный путь к файлу - архив создаётся пустым. Проверяйте существование файлов через file_exists(). Если файл заблокирован другим процессом, addFile() вернёт false.
Как извлечь файлы из архива?
$zip = new ZipArchive();
if ($zip->open('example.zip') === true) {
$zip->extractTo('./extracted/');
$zip->close();
echo 'Извлечение выполнено';
}Целевая директория должна существовать или быть создаваемой (метод extractTo() создаёт поддиректории, но не родительские). Для извлечения отдельных записей используйте второй параметр - массив имён файлов.
Проблема: права доступа. На Windows игнорируются, на Linux файлы получают права текущего процесса. Используйте chmod() после извлечения, если требуется сохранить исходные атрибуты (хранятся в записи, но не применяются автоматически).
Как добавить строку в архив без сохранения на диск?
$zip = new ZipArchive();
if ($zip->open('memory.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
$zip->addFromString('readme.txt', 'Содержимое файла');
$zip->addFromString('config/settings.ini', "debug=1\nlog=1");
$zip->close();
// Файл можно сразу отправить в вывод
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="archive.zip"');
readfile('memory.zip');
unlink('memory.zip');
}Метод addFromString() принимает локальное имя и содержимое. Полезно для динамической генерации архива без временных файлов. Обратите внимание на флаг OVERWRITE - он перезаписывает архив, если он уже существует.
Ошибка: открытие архива в режиме ZipArchive::OVERWRITE без CREATE приведёт к ошибке, если файл не существует. Используйте побитовое ИЛИ: ZipArchive::CREATE | ZipArchive::OVERWRITE.
Как рекурсивно добавить директорию?
function addDirToZip(ZipArchive $zip, string $dir, string $localPath = '') {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
$localName = $localPath . $iterator->getSubPathname();
$zip->addFile($file->getPathname(), $localName);
}
}
$zip = new ZipArchive();
$zip->open('folder.zip', ZipArchive::CREATE);
addDirToZip($zip, '/path/to/folder', 'folder/');
$zip->close();Используется RecursiveDirectoryIterator для обхода всех поддиректорий. Параметр $localPath задаёт базовую папку внутри архива. Без него файлы будут добавлены с полным путём относительно корня архива.
Проблема: символические ссылки. RecursiveDirectoryIterator по умолчанию следует по ссылкам, что может привести к зацикливанию. Используйте флаг RecursiveDirectoryIterator::FOLLOW_SYMLINKS (по умолчанию включён) с осторожностью. Для исключения ссылок добавьте проверку $file->isLink().
Как проверить целостность архива?
$zip = new ZipArchive();
if ($zip->open('test.zip') === true) {
$status = $zip->getStatusString();
if ($status === 'No error') {
echo 'Архив корректен';
} else {
echo "Ошибка: $status";
}
$zip->close();
}Метод getStatusString() возвращает описание последней ошибки. Также можно использовать statusSys и status свойства, но они устарели. Полная проверка требует извлечения или вызова getArchiveComment(), но для базовой целостности достаточно статуса.
Важно: открытие повреждённого архива может вернуть true, но при попытке чтения записей возникнут ошибки. Всегда проверяйте результат open() и используйте count() для проверки количества файлов.
Как получить список файлов без извлечения?
$zip = new ZipArchive();
if ($zip->open('data.zip') === true) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
echo $stat['name'] . ' (размер: ' . $stat['size'] . ' байт)
';
}
$zip->close();
}Метод statIndex() возвращает массив с информацией о записи: имя, размер, сжатый размер, CRC32, дата. Альтернатива - getNameIndex() для получения только имени.
Ошибка: если архив пуст, numFiles равен 0, и цикл не выполнится. Проверяйте $zip->numFiles > 0 перед обходом.
Как защитить архив паролем (AES-256)?
$zip = new ZipArchive();
if ($zip->open('secure.zip', ZipArchive::CREATE) === true) {
$zip->setPassword('secret123');
$zip->addFile('doc.txt', 'doc.txt');
$zip->setEncryptionName('doc.txt', ZipArchive::EM_AES_256);
$zip->close();
echo 'Архив с паролем создан';
}Шифрование доступно только если libzip собрана с поддержкой AES. Метод setEncryptionName() принимает имя файла и метод шифрования (EM_AES_256, EM_AES_128, EM_TRADITIONAL). Пароль задаётся перед добавлением файла.
Проблема: на старых версиях PHP (до 7.4) шифрование не поддерживается. Проверьте ZipArchive::ENCRYPTION_METHOD_AES_256 константу. Если она не определена, используйте внешние утилиты (например, 7z). Также пароль не применяется к уже добавленным файлам - нужно устанавливать перед каждым addFile() или addFromString().
Расширенные примеры работы с ZipArchive
1. Создание архива с разными уровнями сжатия
$zip = new ZipArchive();
$zip->open('compress.zip', ZipArchive::CREATE);
$zip->setCompressionName('image.png', ZipArchive::CM_STORE); // без сжатия
$zip->setCompressionName('text.txt', ZipArchive::CM_DEFLATE, 9); // максимальное сжатие
$zip->addFile('image.png');
$zip->addFile('text.txt');
$zip->close();Файл image.png будет сохранён без сжатия (быстро, но архив больше), text.txt - с deflate уровнем 9 (медленнее, но компактнее).
2. Извлечение только определённых файлов по маске
$zip = new ZipArchive();
$zip->open('backup.zip');
$files = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (strpos($name, 'logs/') === 0) {
$files[] = $name;
}
}
$zip->extractTo('./extr/', $files);
$zip->close();Будут извлечены только файлы из папки logs/ (например, logs/error.log, logs/access.log).
3. Удаление файла из существующего архива
$zip = new ZipArchive();
if ($zip->open('archive.zip') === true) {
$zip->deleteName('old.txt');
$zip->close();
echo 'Файл удалён';
} else {
echo 'Не удалось открыть архив';
}Метод deleteName() удаляет запись по имени. Если файл не найден, возвращает false, но ошибка не генерируется. После удаления архив необходимо пересобрать - изменения применяются при close().
4. Работа с комментариями архива и файлов
$zip = new ZipArchive();
$zip->open('comm.zip', ZipArchive::CREATE);
$zip->setArchiveComment('Архив проекта v2.1');
$zip->addFromString('readme.txt', 'Содержимое');
$zip->setCommentName('readme.txt', 'Этот файл описывает проект');
$zip->close();
// Чтение комментариев
$zip->open('comm.zip');
echo 'Архив: ' . $zip->getArchiveComment() . "
";
echo 'Файл: ' . $zip->getCommentName('readme.txt');
$zip->close();Архив: Архив проекта v2.1
Файл: Этот файл описывает проект
5. Создание архива в памяти и потоковая отправка
ob_start();
$zip = new ZipArchive();
$tmp = tempnam(sys_get_temp_dir(), 'zip');
$zip->open($tmp, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$zip->addFromString('data.json', '{"key":"value"}');
$zip->addFromString('readme.txt', 'Streaming example');
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');
header('Content-Length: ' . filesize($tmp));
readfile($tmp);
unlink($tmp);
ob_end_flush();Браузер получит zip-файл для скачивания без сохранения на сервере (временный файл удаляется после отправки).
6. Рекурсивное добавление с исключением определённых расширений
function addDirFiltered($zip, $dir, $localPath, $exclude = ['.git', '.log']) {
$dirIterator = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($dirIterator, RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
$ext = $file->getExtension();
if (in_array($ext, $exclude)) continue;
$localName = $localPath . $iterator->getSubPathname();
if ($file->isDir()) {
$zip->addEmptyDir($localName);
} else {
$zip->addFile($file->getPathname(), $localName);
}
}
}
$zip = new ZipArchive();
$zip->open('project.zip', ZipArchive::CREATE);
addDirFiltered($zip, '/var/www/project', 'project/', ['.git', 'log', 'tmp']);
$zip->close();Архив будет содержать все файлы и папки, кроме тех, чьё расширение или имя входит в массив $exclude. Обратите внимание: для директорий вызывается addEmptyDir(), чтобы сохранить структуру папок даже если они пусты.
7. Извлечение с переименованием файлов
$zip = new ZipArchive();
$zip->open('archive.zip');
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
$newName = 'prefix_' . $name;
// Извлекаем во временную строку и сохраняем под новым именем
$content = $zip->getFromIndex($i);
file_put_contents('./output/' . $newName, $content);
}
$zip->close();Каждый файл из архива будет сохранён с префиксом 'prefix_' в указанной папке. Результат: файл 'doc.txt' станет 'prefix_doc.txt'.
8. Обработка ошибок с использованием исключений
$zip = new ZipArchive();
$result = $zip->open('missing.zip');
if ($result !== true) {
throw new RuntimeException("Не удалось открыть архив. Код ошибки: $result");
}
try {
$zip->addFile('nonexistent.txt');
$zip->close();
} catch (Throwable $e) {
echo 'Ошибка при добавлении: ' . $e->getMessage();
$zip->close();
}Коды ошибок open(): 1 (нет ошибки), 2 (файл не найден), 3 (повреждён), 4 (ошибка ввода/вывода), 5 (недостаточно памяти), 6 (иное).