定制 aurynx/http-compression 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

aurynx/http-compression

Composer 安装命令:

composer require aurynx/http-compression

包简介

Framework-agnostic HTTP compression for PHP (gzip, brotli, zstd) with deterministic builds and safe file precompression.

README 文档

README

Aurynx Mascot

Modern PHP library for HTTP compression with native type safety

gzip • brotli • zstd — simple, safe, and fast

InstallationQuick StartFeaturesUse CasesAPIAdvanced UsageAI Guide

Why HttpCompression?

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

Installation

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

Quick Start

Single File Compression

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\HttpCompression\Enums\AlgorithmEnum;

// Compress and save to file
CompressorFacade::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);

Batch Compression

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\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";

Features

✨ Native Type Safety

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:

public function compress(ItemConfig $config): CompressionResult

🎯 Fluent Facade API

Two facades for different scenarios:

CompressorFacade::make() — Batch compression

CompressorFacade::make()
    ->addFile('index.html')
    ->addGlob('assets/*.css')
    ->withDefaultConfig(ItemConfig::create()->withGzip(9)->build())
    ->toDir('./output')
    ->compress();

CompressorFacade::once() — Quick single-item tasks

CompressorFacade::once()
    ->file('logo.svg')
    ->withGzip(9)
    ->saveTo('logo.svg.gz');

🚀 Glob Pattern Support

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();

💾 Memory-Safe Streaming

Handle large files without loading into memory:

use Aurynx\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
});

🔌 Callback Streaming (no resources)

Stream compressed data directly into callbacks without dealing with stream resources.

Single algorithm (sendToCallback):

use Aurynx\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):

use Aurynx\HttpCompression\CompressorFacade;

$gz = '';
CompressorFacade::once()
    ->data('payload')
    ->withGzip(6)      // required by default
    ->tryBrotli(4)     // optional
    ->sendAllToCallbacks([
        'gzip' => static function (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

🛡️ Fail-Fast Validation

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

📈 Rich Result Objects

Detailed statistics and easy access:

$result = CompressorFacade::make()
    ->addGlob('*.html')
    ->withDefaultConfig(ItemConfig::create()->withGzip(9)->withBrotli(11)->build())
    ->inMemory()
    ->compress();

// Access results
foreach ($result as $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";

Use Cases

1. Static Site Pre-Compression

Compress assets during build for nginx gzip_static:

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\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;

2. Dynamic HTTP Response Compression

Compress content on-the-fly with caching:

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\HttpCompression\AlgorithmEnum;

function compressResponse(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;
}

3. API Response Compression

Compress JSON API responses:

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\HttpCompression\AlgorithmEnum;

function compressApiResponse(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()];
echo compressApiResponse($data, $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '');

4. Log File Archival

Compress and archive old log files:

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\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 originals
foreach ($result->successes() as $id => $item) {
    $originalPath = "storage/logs/{$id}";
    if (file_exists($originalPath)) {
        unlink($originalPath);
    }
}

echo "Archived {$result->count()} log files\n";

5. Asset Pipeline Integration

Integrate with your build tools:

use Aurynx\HttpCompression\CompressorFacade;
use Aurynx\HttpCompression\ValueObjects\ItemConfig;

class AssetCompiler
{
    public function compile(): 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()) {
            throw new \RuntimeException('Asset compression failed');
        }
        
        $summary = $result->summary();
        $avgRatio = $summary->getAverageRatio(AlgorithmEnum::Gzip);
        echo "✓ Compressed {$result->count()} assets (avg ratio: " . round($avgRatio * 100, 1) . "%)\n";
    }
}

API Reference

Facades

CompressorFacade::make() — Batch Compression

use Aurynx\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;

CompressorFacade::once() — Single Item

use Aurynx\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

Notes on Saving Files

  • 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

Configuration

ItemConfig — Compression Configuration

use Aurynx\HttpCompression\ValueObjects\ItemConfig;
use Aurynx\HttpCompression\ValueObjects\AlgorithmSet;

// Using builder
$config = ItemConfig::create()
    ->withGzip(9)
    ->withBrotli(11)
    ->withZstd(3)
    ->limitBytes(5_000_000)
    ->build();

// Direct instantiation
$config = new ItemConfig(
    algorithms: AlgorithmSet::gzip(9),
    maxBytes: 1_000_000
);

// Static factories
$config = ItemConfig::gzip(9);
$config = ItemConfig::brotli(11);
$config = ItemConfig::zstd(3);

AlgorithmSet — Algorithm Configuration

use Aurynx\HttpCompression\ValueObjects\AlgorithmSet;
use Aurynx\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],
]);

Results

CompressionResult — Batch Results

$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

// Iteration
foreach ($result as $id => $item) {
    // Process each item
}

CompressionItemResult — Single Item Result

$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

CompressionSummaryResult — Aggregated Statistics

$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

For AI Assistants

This library is designed to be AI-friendly with:

  • Native types — no docblock parsing needed
  • Explicit namingCompressionResult, 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 schema docs/ai-manifest.json.

Common Patterns

// Quick compression
CompressorFacade::once()->file('test.txt')->withGzip(9)->saveTo('test.txt.gz');

// Batch with glob
CompressorFacade::make()
    ->addGlob('*.html')
    ->withDefaultConfig(ItemConfig::create()->withGzip(9)->build())
    ->toDir('./out')
    ->compress();

// Multiple algorithms
$config = ItemConfig::create()
    ->withGzip(9)
    ->withBrotli(11)
    ->withZstd(3)
    ->build();

Avoid These Mistakes

❌ Multiple algorithms with saveTo():

// WRONG - saveTo() requires exactly one algorithm
CompressorFacade::once()->file('x')->withGzip()->withBrotli()->saveTo('x.gz');

✅ Use compress() instead:

$result = CompressorFacade::once()->file('x')->withGzip()->withBrotli()->compress();
$result->getData(AlgorithmEnum::Gzip);

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

Development Setup

# Install dependencies
composer install

# Run tests
composer test

# Run PHPStan
composer phpstan

# Run CS Fixer
composer cs-fix

License

The MIT License (MIT). Please see License File for more information.

Credits

Created and maintained by Anton Semenov.

Crafted by Aurynx 🔮

aurynx/http-compression 适用场景与选型建议

aurynx/http-compression 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 4 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 10 月 10 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「php」 「http」 「compression」 「compressor」 「static」 「nginx」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 aurynx/http-compression 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 aurynx/http-compression 我们能提供哪些服务?
定制开发 / 二次开发

基于 aurynx/http-compression 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 4
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 31
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-10-10