Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Archivestream Php Laravel Package

barracudanetworks/archivestream-php

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer and check what you actually got before writing code:

composer require barracudanetworks/archivestream-php
composer show barracudanetworks/archivestream-php   # version, PHP constraint, psr/http-message constraint

Then read, in this order:

  1. README.md — the usage block is short and is the authoritative API reference; there is essentially no other documentation.
  2. src/ — you will find ZipStreamer and TarStreamer classes plus the shared interfaces. The core surface is addFileFromPath(), addFileFromString(), addFileFromStream(), and finalize().
  3. composer.json — confirm the PHP version constraint and the psr/http-message requirement, because that is what determines how you wire it into a framework.

The key design fact: the streamer is the output. It implements Psr\Http\Message\StreamInterface, so you add entries, call finalize() (which writes the ZIP central directory / TAR trailer), and then consume the stream. In a PSR-7 stack (Slim, Laminas) you pass it straight to withBody(). In Laravel/Symfony you read it in a loop inside a streamed response.

First use case — a download endpoint that zips a handful of records without ever touching disk:

use ZipStreamer\ZipStreamer;

Route::get('/exports/{id}', function (string $id) {
    $zip = new ZipStreamer();
    $zip->addFileFromString('README.txt', "Export {$id}\n");
    $zip->addFileFromPath('report.csv', storage_path("exports/{$id}.csv"));
    $zip->finalize();

    return response()->stream(function () use ($zip) {
        while (! $zip->eof()) {
            echo $zip->read(8192);
            flush();
        }
    }, 200, [
        'Content-Type'        => 'application/zip',
        'Content-Disposition' => 'attachment; filename="export.zip"',
        'X-Accel-Buffering'   => 'no',
    ]);
}, )->middleware('auth');

That is the "first use case" this package exists for: an archive whose size you do not know and do not want to materialise on disk.

Implementation Patterns

Wrap it in a small service, not in controllers. The streamer has no opinion about your app; put the entry-building logic in something like App\Exports\ZipExportBuilder that returns a finalised streamer. Controllers then only deal with HTTP headers, and tests can build archives without booting HTTP.

Feed it from Laravel's storage layer. Do not download S3 objects to a temp file first:

foreach ($paths as $path) {
    $handle = Storage::disk('s3')->readStream($path);      // remote read stream
    $zip->addFileFromStream(basename($path), $handle);
    fclose($handle);                                        // close as soon as it's added
}

This is the main reason to prefer this library over ZipArchive: ZipArchive needs a real file, so a multi-GB S3 archive means multi-GB local disk churn.

Build CSV entries with php://temp. Generate rows incrementally, write to a php://temp handle (which spills to disk past 2 MB), rewind, and hand the handle to addFileFromStream(). Combine with ->lazy()/chunk() on the query so memory stays flat regardless of row count.

Combine with TAR for Unix-facing exports. TarStreamer produces a plain TAR suitable for tar -x on servers or for piping into your own gzip stage. Use ZIP for end users (Windows Explorer, macOS Finder) and TAR when the consumer is a script or CI job.

PSR-7 stacks get a one-liner:

$response = $response->withBody($zip)
    ->withHeader('Content-Type', 'application/zip')
    ->withHeader('Content-Disposition', 'attachment; filename="export.zip"');

Test by re-opening the archive, not by asserting bytes. Capture the stream into a php://temp handle, write it out inside the test, and assert with ZipArchive::open() / PharData on the entry list and contents. Byte-level assertions will break on any internal change and prove nothing.

Queue jobs are the wrong shape. Streaming assumes a live request and a client that reads. If an export must be retried, emailed, or cached, produce the file in a job using the same streamer but write it to a storage disk (or a multipart S3 upload) instead of to echo. Keep that distinction explicit in your codebase, because "just move it to a queue" is the first thing someone will try.

Gotchas and Tips

  • finalize() is not optional. Without it the ZIP has no central directory and unzip tools reject it. Finalise before the first read, and make it the last thing your builder does.
  • There is no Content-Length. Never set one; the size is unknown until the last byte. This also means no range requests/resume — do not advertise Accept-Ranges.
  • Every buffering layer defeats the point. Check, in order: PHP output_buffering / zlib.output_compression (ob_end_clean() before streaming if needed), nginx proxy_buffering (the X-Accel-Buffering: no header above), Apache mod_deflate on application/zip, and any Laravel response middleware or debug toolbar (Debugbar/Clockwork can inspect and therefore buffer the body — disable them on these routes).
  • Long-running requests: set_time_limit(0), and check connection_aborted() between chunks so an abandoned download stops pulling from S3.
  • The stream is one-shot and non-seekable. rewind() will not work and getSize() returns null. Anything that calls __toString() on the stream (some HTTP clients, some middleware) will slurp the entire archive into memory and silently undo the whole benefit. Grep for code paths that stringify response bodies.
  • Composer/version risk. Last release was May 2020. Verify it installs and runs under your PHP version in CI before committing to it; check the psr/http-message constraint against what your framework requires (composer why-not is your friend). If you hit friction, maennchen/zipstream-php is the actively maintained equivalent and is a drop-in conceptual replacement for the ZIP half.
  • Sanitise archive entry names. User-supplied filenames containing ../, leading /, or NUL bytes produce path traversal on extraction or outright invalid archives. Also deduplicate names — two identical entries make some extractors overwrite silently.
  • Encoding: non-ASCII filenames rely on the UTF-8 flag in the ZIP header. Test with a real accented/CJK name on Windows Explorer, not just with unzip on Linux.
  • Large files and ZIP64: anything crossing 4 GB or 65 535 entries needs ZIP64. Confirm your version handles it; do not discover this in production. For already-compressed payloads (JPEG, MP4), prefer "store" over "deflate" if the API exposes the choice — it saves CPU for zero size benefit.
  • Errors after the first byte cannot change the status code. Validate inputs, resolve all paths and permissions, and build the entry list before creating the streamer. Inside the stream callback, catch exceptions and log them; echoing an error page into a half-written ZIP is worse than a truncated download.
  • Extension points are thin: no events or hooks. The practical approach is composition (a builder class) or a decorator implementing StreamInterface that counts bytes or adds logging. Subclassing the streamer is possible but couples you to its internals.
  • Don't hold file handles open longer than needed. An exception between readStream() and addFileFromStream() leaks a descriptor per file; wrap acquisitions in try/finally.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource