Работа с ZIP архивами: от создания до извлечения в 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