Расширение Zip для PHP: создание, извлечение и управление архивами

Раздел: Расширения 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 (иное).

Расширение Zip для PHP - comments

En
Php zip extension (php)