digitaledgeit/compression
A Laravel/PHP compression package that adds middleware for compressing outgoing HTTP responses, reducing bandwidth and improving load times for text-based content. It integrates into the request lifecycle with minimal configuration and helps serve pages and assets more efficiently.
Install and confirm it still fits your stack first. This package's last release was March 2016 and it has essentially no dependents, so treat it as "evaluate before adopting" rather than "composer require and go."
composer require digitaledgeit/compression
Before writing a line of application code, check three things:
composer.json in the package — supported PHP range. If it caps at ~5.5/~7.0, you'll need --ignore-platform-reqs or a fork; on PHP 8.x expect deprecation notices at minimum.src/ — this package is thin. The entire public surface is likely one archive class plus one or two adapter classes (a ZipArchive-backed adapter and a PharData-backed adapter). Read those files end-to-end; there is no meaningful documentation beyond the README, and the source is the spec.tests/ — the test suite doubles as usage examples and tells you which adapter the author actually exercised.Extensions required. The package is a high-level wrapper, so the underlying extension must be present: ext-zip for the Zip adapter, ext-phar for the Tar/Phar adapter. Verify in php -m and in your composer.json require block (ext-zip: *) so CI fails loudly instead of at runtime inside a queued job.
First use case that pays off immediately: bundling generated artifacts for download. A report exporter that produces N PDFs/CSVs into a temp directory and hands the user one .zip is the canonical fit:
// Illustrative — confirm exact class names/namespace against src/ first.
$archive = new \DigitalEdgeIT\Compression\Archive();
$archive->addFile(storage_path('app/exports/report.pdf'), 'report.pdf');
$archive->addFile(storage_path('app/exports/data.csv'), 'data.csv');
$archive->save(storage_path('app/exports/bundle.zip'));
return response()->download(storage_path('app/exports/bundle.zip'))
->deleteFileAfterSend(true);
If that snippet works after adjusting names, the package is doing its job: it hides ZipArchive::open / addFile / close boilerplate and the flag-juggling that comes with it.
Wrap it, don't sprinkle it. With zero dependents and no active maintenance, the package should never appear in more than one or two places in your codebase. Put it behind a small application-level service so you can swap in ZipArchive directly, or a maintained alternative, without touching callers:
namespace App\Services;
use DigitalEdgeIT\Compression\Archive;
class ZipBuilder
{
public function build(array $files, string $destination): string
{
$archive = new Archive();
foreach ($files as $absolutePath => $entryName) {
$archive->addFile($absolutePath, $entryName);
}
$archive->save($destination);
return $destination;
}
}
Bind it in a service provider ($this->app->bind(ZipBuilder::class, ...)) and inject it. This is also where you'd centralize temp-disk selection, filename sanitization, and cleanup.
Bridge Laravel's Storage facade via local paths. The package almost certainly expects filesystem paths, not stream resources or Storage objects. For non-local disks, stage to a temp location first:
$tmp = Storage::disk('local')->path('tmp/'.Str::uuid());
Storage::disk('s3')->download('invoices/2024-01.pdf', $tmp);
$builder->build([$tmp => 'invoices/2024-01.pdf'], $zipPath);
unlink($tmp);
Never pass Storage::url() or a stream — path-based addFile calls will simply fail (often returning false rather than throwing) when given a non-path.
Do the work in a job for anything non-trivial. Zipping is CPU- and I/O-bound and ZipArchive is synchronous. For exports above a few MB, queue it:
class BuildExportArchive implements ShouldQueue
{
use WithoutOverlapping;
public int $timeout = 300;
public function handle(ZipBuilder $builder): void
{
$path = $builder->build($this->collectFiles(), $this->destination());
Export::whereKey($this->exportId)->update([
'archive_path' => $path,
'ready_at' => now(),
]);
}
}
Notify the user when ready_at is set and serve the file through a signed, short-lived route rather than exposing the path.
Use a consistent entry-name convention. Build the archive with a top-level folder (export-2024-05-01/report.pdf) so it extracts cleanly instead of scattering files into the user's Downloads folder. Do this at the call site — the package won't do it for you.
Test with fixtures, not generated content. Commit a couple of small binary fixtures and assert on the extracted contents (ZipArchive::extractTo into a temp dir, then compare), not on byte-for-byte archive equality — timestamps and compression levels make archives non-deterministic.
Compose multiple adapters behind one interface. If the package exposes an archive interface plus Zip and Tar adapters, type-hint the interface in your service and select the adapter via config. That's the main architectural reason to prefer this package over raw ZipArchive — a uniform API across formats.
It is effectively unmaintained. Last release 2016, 2 stars, 0 dependents. Expect no fixes for PHP 8.x issues (return type deprecations, ZipArchive constant changes, null-passing to non-nullable internals). Mitigations: fork it into your org, or pin it and keep the wrapper boundary thin so removal is a one-file change. For a new project, ZipArchive directly is often less risk than a 2016 wrapper around it.
close() is where errors surface. ZipArchive (and any thin wrapper over it) reports many failures at close(), not at addFile(). Check the return value of your save()/close() and treat false as an exception:
if ($archive->save($path) === false) {
throw new \RuntimeException('Archive could not be written: '.$path);
}
Silent false from addFile() on a missing/unreadable source file is the single most common cause of "the zip is empty / missing files."
Path traversal on extraction. If you ever expose an extract path (uploaded zip → unpack), validate every entry name: reject absolute paths, .. segments, and symlinks. This is a classic zip-slip vector and a 2016-era wrapper will not protect you. Best practice: never extractTo() user-supplied archives into a served directory.
Compression level vs. CPU. PDFs, JPEGs, PNGs, and already-gzipped files don't compress further. If the adapter exposes a per-file method/level, use store for those; you'll cut CPU significantly on large media bundles. If it doesn't expose levels, that's a reason to bypass the wrapper for that case.
Zip64 for >4 GB or >65,535 entries. Neither ZipArchive nor this wrapper handles the limit transparently on all platforms. If exports can approach those sizes, test it explicitly; otherwise chunk into multiple archives.
phar.readonly for Phar-based adapters. PharData can create non-executable .tar/.zip archives without touching phar.readonly, but anything involving Phar proper requires phar.readonly=0 in php.ini, which you typically cannot set at runtime (ini_set is too late). Don't discover this in production; assert it in your deploy checklist if you use a Phar adapter.
Temp file hygiene in Laravel. Use Storage::disk('local')->path('tmp/...') rather than sys_get_temp_dir() + manual paths, and clean up in a finally block or via a scheduled tmp:prune command. Queued exports that fail mid-build are the usual source of orphaned partial zips — note that a partially written archive may still be a valid (truncated) zip, so delete on failure rather than leaving it for the download route.
Filename encoding. Non-ASCII filenames in archives are a cross-platform minefield (Windows Explorer vs. unzip vs. macOS). Set the UTF-8 filename flag if the adapter exposes it; otherwise transliterate/sanitize entry names to ASCII, which is the safer default for user-facing downloads.
Debugging toolkit. When an archive is wrong, drop the wrapper and inspect directly:
$zip = new \ZipArchive();
$zip->open($path);
for ($i = 0; $i < $zip->numFiles; $i++) {
dump($zip->statIndex($i));
}
dump($zip->getStatusString());
Compare that against what you thought you added. Also check umask/permissions on the destination directory — an unwritable directory produces a false return that looks identical to a bad file path.
Extension point is the adapter layer. If you must extend behavior (custom naming, filtering, logging), subclass or replace the adapter rather than editing vendor code, and register your implementation in the container. Given the package size, a hard fork is entirely reasonable and probably preferable to patches/composer patch plugins for anything beyond a one-line fix.
How can I help you explore Laravel packages today?