Laravel-Zipstream: потоковая генерация ZIP-архивов без нагрузки на сервер

Если пользователю нужно скачать десятки или сотни файлов одним архивом, классический подход быстро начинает тормозить. Сервер сначала собирает 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 и балансировщики могут по-разному обрабатывать потоковые ответы.

Если один и тот же архив скачивается часто и не меняется, возможно, выгоднее один раз собрать его и кэшировать.

Комментарии (0)

Войдите, чтобы оставить комментарий

Похожие статьи