Работа с ZIP‑архивами в PHP: создание и распаковка файлов

В веб‑разработке нередко возникает задача упаковать файлы в ZIP‑архив или, наоборот, распаковать его. В PHP для этого есть встроенный класс ZipArchive — он не требует сторонних библиотек и покрывает большинство типовых сценариев.

Ниже разберём, как создавать ZIP‑архивы и извлекать из них файлы, с примерами и пояснениями.


Подготовка: проверка доступности расширения

Класс ZipArchive входит в стандартное расширение zip. Прежде чем писать код, убедитесь, что оно включено в вашей установке PHP. Проще всего проверить через:

php
<?php
var_dump(class_exists('ZipArchive'));

Если вернётся bool(true) — можно работать. Если false, проверьте php.ini: должно быть включено extension=zip (или php_zip.dll на Windows).


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

Базовый пример: добавление одного файла

Допустим, у нас есть файл readme.txt в папке проекта, и мы хотим положить его в архив archive.zip.

php
<?php
$zip = new ZipArchive();
$filename = 'archive.zip';
 
// Создаём архив; флаг ZipArchive::CREATE создаёт архив, если его нет
if ($zip->open($filename, ZipArchive::CREATE) !== true) {
    die('Не удалось создать архив');
}
 
// Добавляем файл в архив
$zip->addFile('readme.txt', 'readme.txt');
 
$zip->close();
echo 'Архив создан успешно';

Здесь второй аргумент addFile — это имя файла внутри архива. Если его не указать, PHP возьмёт путь к файлу как имя внутри архива.

Добавление нескольких файлов и папок

Часто нужно упаковать целую директорию. Для этого удобно рекурсивно обойти папку и добавить файлы:

php
<?php
function addFilesFromDir($zip, $sourceDir, $baseDir = '') {
    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($sourceDir),
        RecursiveIteratorIterator::SELF_FIRST
    );
 
    foreach ($files as $file) {
        if (!$file->isFile()) {
            continue;
        }
 
        $filePath = $file->getRealPath();
        $relativePath = preg_replace('#^' . preg_quote($sourceDir, '#') . '#', '', $filePath);
 
        // Убираем ведущий слэш, чтобы структура была корректной
        $relativePath = ltrim($relativePath, DIRECTORY_SEPARATOR);
 
        // Если задана базовая папка, добавляем её к пути внутри архива
        $archivePath = $baseDir ? $baseDir . DIRECTORY_SEPARATOR . $relativePath : $relativePath;
 
        $zip->addFile($filePath, $archivePath);
    }
}
 
$zip = new ZipArchive();
if ($zip->open('site-backup.zip', ZipArchive::CREATE) !== true) {
    die('Не удалось создать архив');
}
 
addFilesFromDir($zip, __DIR__ . '/public', 'public');
$zip->close();
 
echo 'Архив site-backup.zip создан';

В этом примере содержимое папки public попадёт в архив с сохранением структуры, но внутри архива будет папка public/.

Важные нюансы при создании архива

  • Права доступа: убедитесь, что у PHP есть права на чтение исходных файлов и запись в директорию, где создаётся архив.
  • Ошибки при добавлении файлов: addFile() возвращает false при ошибке. Можно проверять результат и логировать проблемы:
    php
    if (!$zip->addFile($filePath, $archivePath)) {
        echo 'Ошибка добавления файла: ' . $filePath . PHP_EOL;
    }
  • Кодировка имён файлов: в ZIP имена файлов могут быть в разных кодировках. В большинстве случаев UTF‑8 работает нормально, но на старых системах возможны проблемы с кириллицей. Если сталкиваетесь с искажёнными именами, можно попробовать явно задать кодировку или использовать альтернативные инструменты (например, консольный zip через exec).

Распаковка ZIP‑архива

Для распаковки используется метод extractTo.

Простой пример: извлечь всё в папку

php
<?php
$zip = new ZipArchive();
$filename = 'archive.zip';
$destination = __DIR__ . '/extracted';
 
if (!is_dir($destination)) {
    mkdir($destination, 0755, true);
}
 
if ($zip->open($filename) !== true) {
    die('Не удалось открыть архив');
}
 
if ($zip->extractTo($destination) === false) {
    die('Ошибка при распаковке');
}
 
$zip->close();
echo 'Файлы извлечены в ' . $destination;

Извлечение отдельных файлов

Иногда нужно достать только конкретные файлы. Для этого можно перебрать содержимое архива и извлечь нужные:

php
<?php
$zip = new ZipArchive();
if ($zip->open('archive.zip') !== true) {
    die('Не удалось открыть архив');
}
 
$destination = __DIR__ . '/partial-extract';
if (!is_dir($destination)) {
    mkdir($destination, 0755, true);
}
 
for ($i = 0; $i < $zip->numFiles; $i++) {
    $name = $zip->getNameIndex($i);
 
    // Допустим, нам нужны только файлы с расширением .txt
    if (pathinfo($name, PATHINFO_EXTENSION) === 'txt') {
        $zip->extractTo($destination, $name);
    }
}
 
$zip->close();

numFiles — общее количество записей в архиве, getNameIndex возвращает имя файла по индексу.


Проверка содержимого архива без распаковки

Полезно сначала посмотреть, что внутри, особенно если архив приходит от пользователя.

php
<?php
$zip = new ZipArchive();
if ($zip->open('archive.zip') !== true) {
    die('Не удалось открыть архив');
}
 
echo '<ul>';
for ($i = 0; $i < $zip->numFiles; $i++) {
    $stats = $zip->statIndex($i);
    echo '<li>' . htmlspecialchars($stats['name']) . ' (' . $stats['size'] . ' байт)</li>';
}
echo '</ul>';
 
$zip->close();

Так можно вывести список файлов и их размеры, не извлекая ничего на диск.


Безопасность и типичные ошибки

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

  • Не извлекайте файлы поверх существующих без проверки. Лучше распаковывать в изолированную временную папку.
  • Контролируйте пути внутри архива. В ZIP могут быть относительные пути вроде ../../etc/passwd. При извлечении это может перезаписать системные файлы. Проверяйте имена файлов перед распаковкой:
    php
    if (strpos($name, '..') !== false || strpos($name, ':') !== false) {
        // Пропускаем потенциально опасные пути
        continue;
    }
  • Ограничивайте размер и количество файлов. Если архив загружается пользователем, добавьте лимиты на общий размер, количество файлов и максимальный размер одного файла.
  • Проверяйте целостность архива. ZipArchive::checkZipConsistency позволяет проверить архив на повреждения (поддерживается не во всех сборках PHP).

Альтернативные подходы

  • Консольные утилиты: на сервере может быть установлен zip и unzip. Тогда можно вызывать их через exec() или shell_exec(). Это удобно для больших архивов и сложных сценариев, но требует осторожности с безопасностью.
  • Библиотеки для сложных случаев: если нужна продвинутая работа с ZIP (шифрование, потоковая обработка, специфические форматы), можно рассмотреть сторонние решения, но для большинства задач ZipArchive достаточно.

Пример: безопасный импорт архива от пользователя

Представим, что пользователь загружает ZIP с файлами статей. Нам нужно:

  1. Проверить, что файл действительно ZIP.
  2. Посмотреть содержимое.
  3. Распаковать только разрешённые типы файлов в изолированную папку.
php
<?php
$uploadFile = $_FILES['archive']['tmp_name'];
$destination = __DIR__ . '/user-uploads/' . uniqid('upload_', true);
mkdir($destination, 0755, true);
 
$zip = new ZipArchive();
if ($zip->open($uploadFile) !== true) {
    die('Файл не является корректным ZIP‑архивом');
}
 
for ($i = 0; $i < $zip->numFiles; $i++) {
    $stats = $zip->statIndex($i);
    $name = $stats['name'];
 
    // Защита от путей с ".."
    if (strpos($name, '..') !== false) {
        continue;
    }
 
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    if (!in_array($ext, ['txt', 'md', 'html'])) {
        // Пропускаем неподдерживаемые типы
        continue;
    }
 
    $zip->extractTo($destination, $name);
}
 
$zip->close();
echo 'Файлы успешно обработаны и сохранены в ' . $destination;

Добавить комментарий