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.
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.
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.response()->streamDownload() plus the maintained maennchen/zipstream-php.Integration feasibility: feasible but not advisable via the bundle.
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.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.
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.
Range without pre-built archives or a stored, seekable format.maennchen/zipstream-php (or splitbrain/php-zipstream / php-zip + temp file) already acceptable to the team? If yes, this package adds nothing.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).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.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:
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.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.
maennchen/zipstream-php + response()->streamDownload() against 1 GB / 5,000-file payloads; measure TTFB, peak memory, wall time, and worker occupancy.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:
request_terminate_timeout, load balancer idle timeout, and CDN. Whichever fires first truncates the archive mid-stream.Failure modes to design for explicitly.
connection_aborted() in the generator loop and bail.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.mod_deflate/gzip on application/zip wastes CPU and can inflate size. Exclude the route.../ 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.
How can I help you explore Laravel packages today?