Работа с ZIP архивами: от создания до извлечения в PHP

Раздел: Разработка на PHP -> Архивация

Основные методы работы с ZIP архивами

В PHP для работы с ZIP архивами используется встроенный класс ZipArchive, доступный при наличии расширения zip. Ниже рассмотрены различные сценарии использования.

Как создать ZIP архив из одного файла с минимальным кодом?

Самый простой способ - открыть или создать архив, добавить файл и закрыть архив. Этот вариант подходит для упаковки одиночных файлов.


<?php
$zip = new ZipArchive();
$zipName = 'example.zip';
if ($zip->open($zipName, ZipArchive::CREATE) === true) {
    $zip->addFile('/path/to/file.txt', 'file.txt');
    $zip->close();
    echo 'Архив создан';
} else {
    echo 'Ошибка создания архива';
}
?>

Типичные проблемы: если файл не существует, addFile() вернёт false; при отсутствии прав на запись open() завершится ошибкой. Проверяйте существование файла перед добавлением и права на директорию архива.

Цель: быстрая упаковка одного файла для скачивания или сохранения.

Как добавить в архив всю директорию вместе с подпапками?

Для рекурсивного добавления используется итератор RecursiveDirectoryIterator в паре с RecursiveIteratorIterator.


<?php
$zip = new ZipArchive();
$zipName = 'backup.zip';
if ($zip->open($zipName, ZipArchive::CREATE) === true) {
    $sourceDir = '/path/to/folder';
    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($sourceDir),
        RecursiveIteratorIterator::LEAVES_ONLY
    );
    foreach ($files as $file) {
        if (!$file->isDir()) {
            $filePath = $file->getRealPath();
            $relativePath = substr($filePath, strlen($sourceDir) + 1);
            $zip->addFile($filePath, $relativePath);
        }
    }
    $zip->close();
}
?>

Возможные ошибки: относительный путь может включать лишние символы, если $sourceDir заканчивается не на слэш; не забудьте удалить корневой слеш из пути. Также проверяйте, что $file->isDir() исключает папки (их не нужно добавлять в ZIP).

Случаи использования: создание резервных копий проектов, упаковка целой папки для передачи.

Как извлечь ZIP архив в заданную директорию?

Метод extractTo распаковывает все содержимое архива в указанную папку.


<?php
$zip = new ZipArchive();
if ($zip->open('backup.zip') === true) {
    $zip->extractTo('/path/to/extract/');
    $zip->close();
    echo 'Распаковка выполнена';
} else {
    echo 'Не удалось открыть архив';
}
?>

Проблемы: если в целевой директории уже есть файлы с теми же именами, они будут перезаписаны; при нехватке прав на запись извлечение не произойдёт. Рекомендуется предварительно проверять существование файлов или использовать уникальные директории.

Использование: развёртывание архивов на сервере, загрузка пользовательских архивов.

Как защитить ZIP архив паролем?

Встроенные средства PHP не поддерживают шифрование архива. Можно использовать системную утилиту zip с опцией -P или 7-Zip через exec().


<?php
$files = 'file1.txt file2.txt';
$archive = 'protected.zip';
$password = 'secret123';
exec("zip -P $password $archive $files", $output, $returnCode);
if ($returnCode === 0) {
    echo 'Защищённый архив создан';
} else {
    echo 'Ошибка создания архива';
}
?>

Ошибки: утилита zip должна быть установлена; пароль может содержать спецсимволы, вызывающие проблемы в командной строке. Используйте escapeshellarg() для параметров. Также шифрование методом -P является слабым; для AES лучше использовать 7-Zip.

Когда применить: передача чувствительных данных через незащищённые каналы.

Как создать архив из строковых данных без временных файлов?

Метод addFromString позволяет добавить содержимое напрямую из переменной, минуя сохранение на диск.


<?php
$zip = new ZipArchive();
$zipName = 'data.zip';
if ($zip->open($zipName, ZipArchive::CREATE) === true) {
    $csvData = "id,name\n1,Alice\n2,Bob";
    $zip->addFromString('users.csv', $csvData);
    $zip->addFromString('note.txt', 'Generated at ' . date('Y-m-d'));
    $zip->close();
}
?>

Внимание: в больших объёмах данных может возникнуть нехватка памяти, так как данные хранятся в ОЗУ до закрытия архива. Альтернатива - использовать потоки с addFile и временными файлами.

Сценарии: создание отчётов, экспорт данных из БД в архивный формат на лету.

Как обработать большие архивы, не превышая лимит памяти?

При работе с архивами большого размера (сотни мегабайт) рекомендуется использовать потоковую передачу и избегать загрузки всего архива в память. Для создания - добавлять файлы по одному, для извлечения - читать содержимое через getStream и выводить по частям.


<?php
// Извлечение одного файла из большого архива
$zip = new ZipArchive();
$zip->open('huge.zip');
$fp = $zip->getStream('bigfile.dat');
$out = fopen('output.dat', 'w');
while (!feof($fp)) {
    fwrite($out, fread($fp, 8192));
}
fclose($out);
fclose($fp);
$zip->close();
?>

Проблемы: если файлов внутри архива много, вызов getStream для каждого может быть медленным. Для ускорения используйте ZipArchive::RDONLY при открытии.

Назначение: работа с архивами логов, медиафайлов, бекапов.

Расширенные примеры работы с ZIP архивами

Ниже представлены подробные примеры с кодом и результатами для углублённого понимания.

Пример 1: Рекурсивное создание архива с фильтром по расширению

Создание архива, содержащего только файлы с расширением .php и .html из заданной директории.

Пример

<?php
$zip = new ZipArchive();
$zipName = 'project_source.zip';
if ($zip->open($zipName, ZipArchive::CREATE) !== true) {
    exit('Не удалось создать архив');
}

$dir = '/var/www/html';
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir)
);
$allowedExtensions = ['php', 'html'];

foreach ($iterator as $file) {
    if ($file->isFile()) {
        $ext = strtolower($file->getExtension());
        if (in_array($ext, $allowedExtensions)) {
            $localPath = substr($file->getRealPath(), strlen($dir) + 1);
            $zip->addFile($file->getRealPath(), $localPath);
        }
    }
}
$zip->close();
echo 'Архив создан: ' . $zipName;
?>
Вывод:
Архив создан: project_source.zip

Содержимое архива (через команду unzip -l):
  ... index.php
  ... about.html
  ... contact.html

Пример 2: Извлечение только файлов, соответствующих маске

Из большого архива извлекаются только CSV файлы.

Пример

<?php
$zip = new ZipArchive();
if ($zip->open('backup.zip') === true) {
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $stat = $zip->statIndex($i);
        $name = $stat['name'];
        if (preg_match('/\.csv$/i', $name)) {
            $zip->extractTo('./extracted', $name);
            echo 'Извлечён: ' . $name . "\n";
        }
    }
    $zip->close();
} else {
    echo 'Ошибка открытия архива';
}
?>
Вывод:
Извлечён: reports/2024-01.csv
Извлечён: reports/2024-02.csv
...

Пример 3: Создание архива на лету с использованием потоков для данных из базы

Извлекаются записи пользователей из MySQL и создаётся архив с JSON-файлами.

Пример

<?php
$zip = new ZipArchive();
$zipName = 'users_export.zip';
if ($zip->open($zipName, ZipArchive::CREATE) !== true) {
    exit('Не удалось открыть архив');
}

// Имитация данных из БД
$users = [
    ['id' => 1, 'name' => 'Анна', 'email' => 'anna@example.com'],
    ['id' => 2, 'name' => 'Борис', 'email' => 'boris@example.com']
];

foreach ($users as $user) {
    $json = json_encode($user, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    $filename = 'user_' . $user['id'] . '.json';
    $zip->addFromString($filename, $json);
}

$zip->close();
echo 'Архив создан: ' . $zipName;
?>
Вывод:
Архив создан: users_export.zip

Проверка содержимого:
$ unzip -l users_export.zip
Archive:  users_export.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       33  2025-03-01 12:00   user_1.json
       35  2025-03-01 12:00   user_2.json
---------                     -------
       68                     2 files

Пример 4: Скрипт резервного копирования с исключением папок и ограничением размера

Создаётся архив указанной папки, исключая директорию cache и файлы более 10 МБ.

Пример

<?php
$source = '/var/www/myapp';
$excludeDirs = ['cache', 'logs'];
$maxSize = 10 * 1024 * 1024; // 10 MB

$zip = new ZipArchive();
$zipName = 'backup_' . date('Ymd_His') . '.zip';
if ($zip->open($zipName, ZipArchive::CREATE) !== true) {
    exit('Не удалось создать архив');
}

$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::LEAVES_ONLY
);

foreach ($iterator as $file) {
    $realPath = $file->getRealPath();
    $relativePath = ltrim(str_replace($source, '', $realPath), DIRECTORY_SEPARATOR);
    $parts = explode(DIRECTORY_SEPARATOR, $relativePath);
    // Проверка, не начинается ли путь с исключённой директории
    if (in_array($parts[0] ?? '', $excludeDirs)) {
        continue;
    }
    if ($file->getSize() > $maxSize) {
        echo 'Пропущен (слишком большой): ' . $relativePath . "\n";
        continue;
    }
    $zip->addFile($realPath, $relativePath);
}

$zip->close();
echo 'Резервная копия сохранена: ' . $zipName;
?>
Вывод:
Пропущен (слишком большой): uploads/video.mp4
Резервная копия сохранена: backup_20250301_123456.zip

ZIP архивы в PHP - comments

En
Php zip file (php)