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

Zip Streamed Response Bundle Laravel Package

wamania/zip-streamed-response-bundle

A Symfony bundle that streams ZIP archives directly to the HTTP response, so you can build and download archives on the fly without temporary files on disk. Useful for large or dynamically generated downloads.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What the package actually is. wamania/zip-streamed-response-bundle is a thin Symfony bundle wrapping a StreamedResponse subclass that emits a ZIP archive as it is built, rather than writing a temp file and serving it. Its value is not the ZIP logic (delegated to a ZipStream library) nor the HTTP logic (delegated to Symfony HttpFoundation); it is the ~100 lines of glue that pipe generator output into php://output with the right Content-Type/Content-Disposition headers. Everything else in the package — DependencyInjection/, WamaniaZipStreamedResponseExtension, services.yml, Resources/config — is Symfony container plumbing that has no meaning in a Laravel application.

Architecture fit: poor as a dependency, moderate as a reference implementation.

  • Laravel has no bundle registration mechanism, so roughly half the package is inert. You would install it and then bypass its entry points entirely.
  • The one portable artifact is the response class, and it happens to be usable: Laravel responses are Symfony HttpFoundation responses under the hood, so a class extending StreamedResponse can be returned directly from a controller and will flow through the middleware stack, terminate(), and test helpers ($response->streamedContent()) without modification.
  • That is a property of Symfony's HttpFoundation, not of this package. The same result is achievable in ~60 lines of first-party code, or with response()->streamDownload() plus the maintained maennchen/zipstream-php.

Integration feasibility: feasible but not advisable via the bundle.

  • Composer install is the first hazard: the last release is dated 2016-10-17, which predates PHP 7.1 typing norms and PHP 8 entirely. Expect to fight composer.json platform constraints, a pinned legacy ZipStream dependency, and a class API (new ZipStream($name, $options), addFile(), explicit finish()) that no longer matches current upstream. Any install will likely require --ignore-platform-reqs or a forked copy in repositories.
  • The transitive dependency is the real decision point. The maintained ecosystem answer is maennchen/zipstream-php (v3, PHP 8.x, active). Adopting this bundle means adopting a 2016 snapshot of a library that has since had breaking API changes — you get the stale fork, not the fixes.

Technical risk: high, concentrated in three areas.

  1. Abandonment. 11 stars, 0 dependents, no release in ~9 years, no security policy. There is no upstream to route a CVE or a PHP 8.4 deprecation through. The bus factor is effectively zero.
  2. Hidden dependency debt. The bundle's constraints will pin or conflict with other packages; a Laravel app on PHP 8.2+ and current Laravel will spend more time resolving Composer than writing code.
  3. Streaming semantics are unforgiving and the package does not defend you. Once the first bytes are flushed, the status code and headers are immutable. If a source file disappears mid-archive, the client receives a truncated ZIP that looks like a completed download. There is no Content-Length, so no progress bar and no integrity check unless you supply a manifest/checksum out-of-band. Filename sanitisation for archive entries is the caller's responsibility.

Key questions to resolve before any code is written.

  • What is the realistic archive size and concurrency? (Streaming wins below ~a few hundred MB and low concurrency; loses badly at scale.)
  • Do we need resumable/range downloads, or is a single-shot download acceptable? ZIP streaming cannot support Range without pre-built archives or a stored, seekable format.
  • Are these archives cacheable by content hash, or genuinely unique per request? If cacheable, object storage + pre-signed URL dominates this design on every axis.
  • Is maennchen/zipstream-php (or splitbrain/php-zipstream / php-zip + temp file) already acceptable to the team? If yes, this package adds nothing.
  • What is the licensing and provenance posture for vendoring abandoned code — who owns the fork?

Integration Approach

Stack fit. Treat this as a pattern to reimplement, not a package to install. The Laravel-native equivalent is:

  • maennchen/zipstream-php for archive construction (maintained, PHP 8.x, PSR-4, no framework coupling).
  • A small application-level ZipStreamedResponse (extending Symfony\Component\HttpFoundation\StreamedResponse, or simply response()->streamDownload(fn () => ..., $name, $headers)) that sets Content-Type: application/zip, Content-Disposition: attachment; filename=..., Cache-Control: no-store, and disables output buffering/compression for that route.
  • A per-request archive spec (list of file sources + archive paths) built by a service, so the ZIP contents are testable independent of HTTP.

This keeps the useful 20% of the bundle, discards the Symfony bundle scaffolding, and gets us onto a dependency with a future.

Migration path. Two viable routes, in order of preference:

  1. Greenfield reimplementation (recommended). No migration. Add maennchen/zipstream-php, write the response/controller pair, delete nothing. Effort: ~0.5 day spike, ~2–3 days to a production-quality version with tests for header correctness, truncated-source behaviour, and large-file memory ceiling.
  2. Vendor extraction (only if something already depends on it). Composer repositories entry pointing at a fork, strip the DependencyInjection/ and Resources/ directories, keep only the Response/ namespace, upgrade the internal ZipStream calls to the v3 API. This is strictly more work for the same outcome and should be rejected unless a legacy application is already in production on it.

Compatibility. Assume the bundle is incompatible with PHP 8.x until proven otherwise; assume its ZipStream dependency is incompatible with any other package requiring maennchen/zipstream-php ^3.0. Do not attempt to run both. Verify Laravel support for streamed responses under the app server actually in use (see below) before committing to the design.

Sequencing.

  1. Confirm the requirement: is the archive content dynamic per request, or can it be pre-built and cached? If cacheable, stop — use object storage.
  2. Spike (0.5 day): maennchen/zipstream-php + response()->streamDownload() against 1 GB / 5,000-file payloads; measure TTFB, peak memory, wall time, and worker occupancy.
  3. Harden (2–3 days): filename sanitisation, missing-source handling before the first byte is written, checksum manifest, structured logging on client aborts.
  4. Load test at target concurrency behind the actual reverse proxy; validate no buffering, no double gzip, no 100-second gateway timeout.
  5. Document the "don't add this bundle" decision in the ADR so it isn't re-litigated.

Operational Impact

Maintenance. Near-zero if we reimplement on the maintained library; unquantifiable if we adopt the bundle. An abandoned dependency is not a one-off setup cost — it is a recurring tax: every PHP minor upgrade, every Composer audit, every framework bump becomes a manual compatibility review with no upstream to consult. For an 11-star package with no dependents, the expected support is "read the source yourself." Budget is better spent on a ~100-line internal component we control.

Support. No maintainer, no issues triage, no security response channel. Our own on-call inherits the streaming code path, and streaming failures are among the least pleasant to debug in production: the client sees a corrupt file, our logs show a 200. Mitigation is instrumentation, not the package — log archive start/finish, item count, bytes emitted, and connection_aborted() outcomes per request.

Scaling. This is the design's real weak point, independent of which package we use. Each download occupies a PHP-FPM worker (or an Octane worker, which is worse — streamed responses under Swoole/RoadRunner tie up a coroutine/worker and can stall the event loop if the generator does blocking disk I/O) for the full duration of the transfer. Compression is CPU-bound and single-threaded per request. Throughput therefore scales with worker count divided by archive size, not with horizontal capacity in any cheap way. Three concrete consequences:

  • Concurrent large downloads become a self-inflicted denial-of-service; consider a queue + pre-built artifact in object storage with a signed URL for anything above a size threshold.
  • Timeouts stack: web server, FPM request_terminate_timeout, load balancer idle timeout, and CDN. Whichever fires first truncates the archive mid-stream.
  • Zero cacheability without a content-hash key; if archives are deterministic, hash the manifest and cache the result, which converts the whole problem into a static file serve.

Failure modes to design for explicitly.

  • Post-header failure: any exception after the first byte yields a well-formed 200 with a corrupt body. Pre-validate every source path and permission before opening the stream.
  • Client disconnect: PHP may continue compressing after the socket closes; check connection_aborted() in the generator loop and bail.
  • Proxy buffering: nginx proxy_buffering on or a corporate proxy will buffer the whole archive, destroying the latency benefit and often hitting memory limits. Requires X-Accel-Buffering: no and a proxy config change.
  • Double compression: mod_deflate/gzip on application/zip wastes CPU and can inflate size. Exclude the route.
  • Filename/path handling: sanitise entry names; a user-supplied name containing ../ produces a hostile archive we distribute under our brand.

Ramp-up. Low for the reimplementation path — a mid-level Laravel engineer with no ZIP or streaming background can deliver it in 2–3 days, and the code is small enough to fully review. High for the bundle path, because the engineer must first learn Symfony bundle conventions that do not apply, then reverse-engineer 2016-era API usage, then still write all the hardening above. There is also an organisational cost: every future engineer who sees wamania/zip-streamed-response-bundle in composer.json will spend time determining whether it is load-bearing.

Recommendation. Reject the bundle as a dependency. Extract the pattern, implement it against maennchen/zipstream-php behind a Laravel-native streaming response, gate archives above a size threshold to a queued pre-build with object storage, and record the decision. Net cost is roughly 3–4 engineer-days plus a load-test cycle; the alternative is an unbounded, unmaintained liability in a path that is hard to debug and easy to get wrong.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings