Если пользователю нужно скачать десятки или сотни файлов одним архивом, классический подход быстро начинает тормозить. Сервер сначала собирает ZIP-файл, сохраняет его на диск, а уже потом отправляет клиенту. При больших объёмах данных это нагружает диск и память, а в контейнерных окружениях легко приводит к переполнению хранилища.
Пакет laravel-zipstream для Laravel предлагает другой сценарий: архив формируется потоково и сразу отправляется в HTTP-ответе. Без временных файлов и без лишней нагрузки на сервер.
В основе решения лежит библиотека ZipStream-PHP, которая умеет собирать ZIP на лету.
Установка
Пакет подключается стандартно через Composer:
composer require exequiel/laravel-zipstreamПосле установки можно сразу использовать его в контроллерах.
Простейший пример: архив из локальных файлов
Допустим, нужно отдать пользователю архив с файлами из storage.
use Zip;
public function download()
{
return Zip::create('documents.zip', [
storage_path('app/files/report1.pdf'),
storage_path('app/files/report2.pdf'),
]);
}Что происходит:
метод create формирует HTTP-ответ;
файлы читаются по очереди;
архив собирается потоково;
браузер начинает скачивание сразу.
Никакого временного ZIP-файла на сервере не создаётся.
Добавление файлов с кастомными именами
Можно управлять тем, как файлы будут называться внутри архива.
use Zip;
public function download()
{
return Zip::create('invoices.zip', [
'invoice-jan.pdf' => storage_path('app/invoices/1.pdf'),
'invoice-feb.pdf' => storage_path('app/invoices/2.pdf'),
]);
}Ключ массива это имя файла в архиве, значение путь к реальному файлу.
Архивация файлов из Laravel Storage
Если вы используете диски Laravel, например S3 или отдельный диск storage, можно работать напрямую через них:
use Zip;
use Illuminate\Support\Facades\Storage;
public function download()
{
$files = [
'avatar.jpg' => Storage::disk('public')->path('avatars/user1.jpg'),
'cv.pdf' => Storage::disk('public')->path('docs/user1.pdf'),
];
return Zip::create('profile.zip', $files);
}Это удобно, когда источники файлов разные, но доступ к ним уже абстрагирован через Storage.
Добавление данных на лету
Пакет позволяет добавлять не только физические файлы, но и строки или сгенерированный контент.
Например, можно вложить CSV, сформированный динамически:
use Zip;
public function export()
{
$csv = "name,email\nJohn,john@example.com\nJane,jane@example.com";
return Zip::create('export.zip', [
'users.csv' => $csv,
]);
}В этом случае файл вообще не существует на диске. Он создаётся в памяти и сразу попадает в поток архива.
Массовая выгрузка из базы данных
Реальный кейс: у вас есть список заказов, и каждый заказ связан с документом. Нужно отдать всё одним архивом.
use Zip;
use App\Models\Order;
public function downloadOrders()
{
$orders = Order::with('document')->get();
$files = [];
foreach ($orders as $order) {
$files["order-{$order->id}.pdf"] = storage_path(
"app/orders/{$order->document->filename}"
);
}
return Zip::create('orders.zip', $files);
}Даже если файлов сотни, сервер не будет держать весь архив в памяти. Данные передаются по мере формирования.
Когда это особенно полезно
Потоковая генерация архива оправдана, если:
архив формируется по запросу пользователя;
файлы занимают гигабайты;
проект работает в Docker с ограниченным дисковым пространством;
одновременно скачивают несколько пользователей.
В таких условиях отказ от временных файлов снижает риск переполнения диска и уменьшает нагрузку.
На что обратить внимание
Перед использованием стоит учесть несколько технических нюансов:
веб-сервер и прокси не должны агрессивно буферизовать ответ;
важно проверить таймауты, если архив формируется долго;
CDN и балансировщики могут по-разному обрабатывать потоковые ответы.
Если один и тот же архив скачивается часто и не меняется, возможно, выгоднее один раз собрать его и кэшировать.