- Notifications
You must be signed in to change notification settings - Fork0
Framework-agnostic PHP library for efficient HTTP compression (gzip, brotli, zstd) — simple, safe, and deterministic.
License
aurynx/http-compression
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Modern PHP library for HTTP compression with native type safety
gzip • brotli • zstd — simple, safe, and fast
Installation •Quick Start •Features •Use Cases •API •Advanced Usage •AI Guide
Modern web applications need efficient compression to reduce bandwidth and improve response times. HttpCompression makes it simple with a clean, modern API focused on:
- 🔷Native PHP 8.4+ types — zero docblock types, full IDE autocomplete
- 🎯Single facade pattern — one intuitive API for all scenarios
- 🚀Glob pattern support — compress entire directories with wildcards
- 💾Memory-safe streaming — handle large files without memory limits
- 🛡️Fail-fast validation — catch errors at configuration time
- 🤖AI-friendly design — perfect for code generation and assistants
Requirements:
- PHP 8.4 or higher
ext-zlib(required for gzip)ext-brotli(optional, for brotli compression)ext-zstd(optional, for zstd compression)
composer require aurynx/http-compression
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\Enums\AlgorithmEnum;// Compress and save to fileCompressorFacade::once() ->file('public/index.html') ->withGzip(9) ->saveTo('public/index.html.gz');// Compress in-memory data$html ='<html><body>Hello World</body></html>';$result = CompressorFacade::once() ->data($html) ->withBrotli(11) ->compress();$compressed =$result->getData(AlgorithmEnum::Brotli);
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\ValueObjects\ItemConfig;$result = CompressorFacade::make() ->addGlob('public/**/*.{html,css,js}') ->withDefaultConfig( ItemConfig::create() ->withGzip(9) ->withBrotli(11) ->build() ) ->skipAlreadyCompressed() ->toDir('./dist') ->compress();echo"Compressed{$result->count()} files\n";echo"Success rate:" . ($result->allOk() ?'100%' :'partial') ."\n";
The public API uses native PHP 8.4+ types everywhere (parameters, return types, readonly DTOs). This makes the library:
- Easier for IDEs and AI agents to navigate (no docblock type guessing)
- Safer at runtime thanks to engine-level type checks
- More self-documenting due to explicit signatures
Example signature:
publicfunction compress(ItemConfig$config):CompressionResult
Two facades for different scenarios:
CompressorFacade::make() ->addFile('index.html') ->addGlob('assets/*.css') ->withDefaultConfig(ItemConfig::create()->withGzip(9)->build()) ->toDir('./output') ->compress();
CompressorFacade::once() ->file('logo.svg') ->withGzip(9) ->saveTo('logo.svg.gz');
Compress entire directories with powerful glob patterns:
CompressorFacade::make() ->addGlob('public/**/*.html')// All HTML files recursively ->addGlob('assets/*.{css,js}')// CSS and JS in assets/ ->addGlob('fonts/*.woff2')// Specific extension ->skipAlreadyCompressed()// Skip images, videos, etc. ->toDir('./dist', keepStructure:true) ->compress();
Handle large files without loading into memory:
useAurynx\HttpCompression\ValueObjects\OutputConfig;$result = CompressorFacade::make() ->addFile('large-file.json')// 500MB file ->withDefaultConfig(ItemConfig::create()->withGzip(6)->build()) ->inMemory(maxBytes:100_000_000)// 100MB limit ->compress();// Stream compressed data$result->first()->read(AlgorithmEnum::Gzip,function (string$chunk) {echo$chunk;// Process in chunks});
Stream compressed data directly into callbacks without dealing with stream resources.
Single algorithm (sendToCallback):
useAurynx\HttpCompression\CompressorFacade;$buffer ='';CompressorFacade::once() ->data(str_repeat('hello',5000)) ->withGzip(6) ->sendToCallback(function (string$chunk)use (&$buffer):void {$buffer .=$chunk;// write to socket, PSR-7 body, etc. });
Multiple algorithms (sendAllToCallbacks):
useAurynx\HttpCompression\CompressorFacade;$gz ='';CompressorFacade::once() ->data('payload') ->withGzip(6)// required by default ->tryBrotli(4)// optional ->sendAllToCallbacks(['gzip' =>staticfunction (string$chunk)use (&$gz):void {$gz .=$chunk; },// 'br' may be omitted when added via tryBrotli() ]);
See more patterns and caveats in Advanced Usage:
- Callback streaming (single/multi)
- Low-level WritableStream wrapper
👉 Read: ./docs/advanced-usage.md
Errors are caught at configuration time, not during compression:
// ❌ Throws immediately (invalid level)AlgorithmSet::gzip(99);// InvalidArgumentException: Level must be between 1 and 9// ❌ Throws immediately (multiple algorithms for saveTo)CompressorFacade::once() ->file('test.txt') ->withGzip(9) ->withBrotli(11)// Multiple algorithms ->saveTo('test.gz');// CompressionException: saveTo() requires exactly one algorithm
Detailed statistics and easy access:
$result = CompressorFacade::make() ->addGlob('*.html') ->withDefaultConfig(ItemConfig::create()->withGzip(9)->withBrotli(11)->build()) ->inMemory() ->compress();// Access resultsforeach ($resultas$id =>$item) {if ($item->isOk()) {echo"Original:{$item->originalSize} bytes\n";echo"Gzip:{$item->compressedSizes['gzip']} bytes\n";echo"Brotli:{$item->compressedSizes['brotli']} bytes\n"; }}// Aggregated statistics$summary =$result->summary();echo"Median compression ratio (gzip):" .$summary->getMedianRatio(AlgorithmEnum::Gzip) ."\n";echo"P95 compression time (brotli):" .$summary->getP95TimeMs(AlgorithmEnum::Brotli) ." ms\n";
Compress assets during build for nginxgzip_static:
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\ValueObjects\ItemConfig;// Build script$result = CompressorFacade::make() ->addGlob('dist/**/*.{html,css,js,svg,json}') ->withDefaultConfig( ItemConfig::create() ->withGzip(9) ->withBrotli(11) ->build() ) ->skipAlreadyCompressed() ->toDir('./dist', keepStructure:true) ->compress();if (!$result->allOk()) {foreach ($result->failures()as$id =>$failure) {echo"Failed:{$id} -{$failure->getFailureReason()?->getMessage()}\n"; }exit(1);}echo"✓ Compressed{$result->count()} files\n";
Nginx configuration:
gzip_static on;brotli_static on;
Compress content on-the-fly with caching:
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\AlgorithmEnum;functioncompressResponse(string$content,string$acceptEncoding):string{$cacheKey ='compressed_' .md5($content) .'_' .$acceptEncoding;if ($cached =apcu_fetch($cacheKey)) {return$cached; }$algo =str_contains($acceptEncoding,'br') ? AlgorithmEnum::Brotli : AlgorithmEnum::Gzip;$result = CompressorFacade::once() ->data($content) ->withAlgorithm($algo,$algo->getDefaultLevel()) ->compress();$compressed =$result->getData($algo);apcu_store($cacheKey,$compressed,3600);return$compressed;}// In your controller$html =view('welcome')->render();$acceptEncoding =$_SERVER['HTTP_ACCEPT_ENCODING'] ??'';if (str_contains($acceptEncoding,'br') ||str_contains($acceptEncoding,'gzip')) {$compressed =compressResponse($html,$acceptEncoding);header('Content-Encoding:' . (str_contains($acceptEncoding,'br') ?'br' :'gzip'));echo$compressed;}else {echo$html;}
Compress JSON API responses:
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\AlgorithmEnum;functioncompressApiResponse(array$data,string$acceptEncoding):string{$json =json_encode($data);if (!str_contains($acceptEncoding,'gzip')) {return$json; }$result = CompressorFacade::once() ->data($json) ->withGzip(6)// Lower level for speed ->compress();header('Content-Encoding: gzip');header('Vary: Accept-Encoding');return$result->getData(AlgorithmEnum::Gzip);}// Usage$data = ['users' => User::all()];echocompressApiResponse($data,$_SERVER['HTTP_ACCEPT_ENCODING'] ??'');
Compress and archive old log files:
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\ValueObjects\ItemConfig;// Daily cron job$result = CompressorFacade::make() ->addGlob('storage/logs/*.log') ->withDefaultConfig(ItemConfig::create()->withZstd(19)->build())// Maximum compression ->toDir('storage/logs/archive', keepStructure:false) ->compress();// Delete originalsforeach ($result->successes()as$id =>$item) {$originalPath ="storage/logs/{$id}";if (file_exists($originalPath)) {unlink($originalPath); }}echo"Archived{$result->count()} log files\n";
Integrate with your build tools:
useAurynx\HttpCompression\CompressorFacade;useAurynx\HttpCompression\ValueObjects\ItemConfig;class AssetCompiler{publicfunctioncompile():void {// Step 1: Bundle and minify (webpack, vite, etc.)system('npm run build');// Step 2: Compress for production$result = CompressorFacade::make() ->addGlob('public/build/**/*.{js,css}') ->addGlob('public/build/**/*.{svg,json}') ->withDefaultConfig( ItemConfig::create() ->withGzip(9) ->withBrotli(11) ->build() ) ->skipExtensions(['woff2','png','jpg']) ->toDir('public/build', keepStructure:true) ->failFast(true) ->compress();if (!$result->allOk()) {thrownew \RuntimeException('Asset compression failed'); }$summary =$result->summary();$avgRatio =$summary->getAverageRatio(AlgorithmEnum::Gzip);echo"✓ Compressed{$result->count()} assets (avg ratio:" .round($avgRatio *100,1) ."%)\n"; }}
useAurynx\HttpCompression\CompressorFacade;$result = CompressorFacade::make()// Add inputs ->add(CompressionInput$input, ?ItemConfig$config =null) ->addMany(iterable$inputs) ->addFile(string$path, ?ItemConfig$config =null, ?string$id =null) ->addData(string$data, ?ItemConfig$config =null, ?string$id =null) ->addGlob(string$pattern, ?ItemConfig$config =null) ->addFrom(InputProviderInterface$provider, ?ItemConfig$config =null)// Configuration ->withDefaultConfig(ItemConfig$config)// Output ->toDir(string$dir, bool$keepStructure =false) ->inMemory(int$maxBytes =5_000_000)// Options ->failFast(bool$enable =true) ->skipExtensions(array$extensions) ->skipAlreadyCompressed()// Execute ->compress(): CompressionResult;
useAurynx\HttpCompression\CompressorFacade;CompressorFacade::once()// Input ->file(string$path) ->data(string$data)// Algorithm (choose ONE) ->withGzip(int$level =6) ->withBrotli(int$level =11) ->withZstd(int$level =3)// Execute ->compress(): CompressionItemResult ->saveTo(string$path): void;// Requires exactly one algorithm
saveTo(path):
- Atomic write (tmp + rename) to the target path
- Existing target is replaced (OverwritePolicy=Replace)
- The target directory must already exist (no auto-create)
saveAllTo(directory, basename, options):
- basename must be a plain filename (no '/' or '\', not '.' or '..')
- Options:
- overwritePolicy: fail|replace|skip (default fail)
- atomicAll: bool (default true) — all-or-nothing semantics
- allowCreateDirs: bool (default true)
- permissions: int|null — chmod after successful rename
useAurynx\HttpCompression\ValueObjects\ItemConfig;useAurynx\HttpCompression\ValueObjects\AlgorithmSet;// Using builder$config = ItemConfig::create() ->withGzip(9) ->withBrotli(11) ->withZstd(3) ->limitBytes(5_000_000) ->build();// Direct instantiation$config =newItemConfig( algorithms: AlgorithmSet::gzip(9), maxBytes:1_000_000);// Static factories$config = ItemConfig::gzip(9);$config = ItemConfig::brotli(11);$config = ItemConfig::zstd(3);
useAurynx\HttpCompression\ValueObjects\AlgorithmSet;useAurynx\HttpCompression\Enums\AlgorithmEnum;// Static factories$set = AlgorithmSet::gzip(9);$set = AlgorithmSet::brotli(11);$set = AlgorithmSet::zstd(3);$set = AlgorithmSet::fromDefaults();// All algorithms with default levels// Manual construction from pairs$set = AlgorithmSet::from([ [AlgorithmEnum::Gzip,9], [AlgorithmEnum::Brotli,11],]);
$result = CompressorFacade::make()->compress();// Access$result->get(string$id): CompressionItemResult$result->first(): CompressionItemResult$result->toArray(): array// Filtering$result->successes(): array$result->failures(): array$result->allOk(): bool// Statistics$result->summary(): CompressionSummaryResult$result->count(): int// Iterationforeach ($resultas$id =>$item) {// Process each item}
$item =$result->first();// Status$item->isOk(): bool$item->success: bool$item->originalSize: int// Data access$item->getData(AlgorithmEnum$algo): string$item->getStream(AlgorithmEnum$algo): resource$item->read(AlgorithmEnum$algo, callable$consumer): void// Metadata$item->has(AlgorithmEnum$algo): bool$item->compressedSizes: array<string, int>$item->compressionTimes: array<string, float>$item->errors: array<string, \Throwable>$item->getFailureReason(): ?\Throwable
$summary =$result->summary();// Compression ratios (compressed / original)$summary->getAverageRatio(AlgorithmEnum$algo): float$summary->getMedianRatio(AlgorithmEnum$algo): float// p50$summary->getP95Ratio(AlgorithmEnum$algo): float// Timing (milliseconds)$summary->getMedianTimeMs(AlgorithmEnum$algo): float// p50$summary->getP95TimeMs(AlgorithmEnum$algo): float$summary->getTotalTimeMs(AlgorithmEnum$algo): float// Counts$summary->getTotalItems(): int$summary->getSuccessCount(): int$summary->getFailureCount(): int
This library is designed to be AI-friendly with:
- ✅Native types — no docblock parsing needed
- ✅Explicit naming —
CompressionResult,AlgorithmEnum, etc. - ✅Fluent API — easy to chain methods
- ✅Fail-fast — errors are obvious and immediate
- ✅Immutable value objects — no side effects
For a deeper, agent-focused walkthrough, see the AI Guide:AI_GUIDE.md. You can also use the machine-readable schemadocs/ai-manifest.json.
// Quick compressionCompressorFacade::once()->file('test.txt')->withGzip(9)->saveTo('test.txt.gz');// Batch with globCompressorFacade::make() ->addGlob('*.html') ->withDefaultConfig(ItemConfig::create()->withGzip(9)->build()) ->toDir('./out') ->compress();// Multiple algorithms$config = ItemConfig::create() ->withGzip(9) ->withBrotli(11) ->withZstd(3) ->build();
❌ Multiple algorithms withsaveTo():
// WRONG - saveTo() requires exactly one algorithmCompressorFacade::once()->file('x')->withGzip()->withBrotli()->saveTo('x.gz');
✅ Usecompress() instead:
$result = CompressorFacade::once()->file('x')->withGzip()->withBrotli()->compress();$result->getData(AlgorithmEnum::Gzip);
Contributions are welcome! Please seeCONTRIBUTING.md for details.
# Install dependenciescomposer install# Run testscomposertest# Run PHPStancomposer phpstan# Run CS Fixercomposer cs-fix
The MIT License (MIT). Please seeLicense File for more information.
Created and maintained byAnton Semenov.
Crafted by Aurynx 🔮
About
Framework-agnostic PHP library for efficient HTTP compression (gzip, brotli, zstd) — simple, safe, and deterministic.
Topics
Resources
License
Code of conduct
Contributing
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
