wyrihaximus/compress-contracts
PHP interfaces for compression. Defines small CompressorInterface and DecompressorInterface contracts so you can swap gzip, zlib, or custom implementations without coupling code to a specific library. Great for ReactPHP and other async projects needing clean, testable compression abstractions.
wyrihaximus/compress-contracts is a tiny contract package: it ships a single marker-style compress interface (namespace convention is WyriHaximus\Compress, method conventionally compress(string $contents): string). It has no Laravel service provider, no config, no facade, and no runtime dependencies. All the actual algorithm work lives in separate implementation packages, so treat this repo as the seam you type-hint against, not as a library you call.
Steps:
composer require wyrihaximus/compress-contracts and confirm the constraint that landed (composer show wyrihaximus/compress-contracts). Last release was 2020-12-24, so the PHP requirement in its composer.json may predate your runtime.src/ first. It is one or two files, and it is the only authoritative statement of the method name, signature, and whether the payload is a string or resource. Confirm the autoloaded namespace in composer.json before copying any snippet below.wyrihaximus/compress does not fit, write your own two-line class over gzencode/gzdecode. The contract is small enough that owning the implementation is normal.new a concrete class anywhere else.// app/Providers/CompressServiceProvider.php
use WyriHaximus\Compress\CompressInterface;
public function register(): void
{
$this->app->bind(CompressInterface::class, fn (): CompressInterface => new GzipCompress(level: 6));
}
First use case: type-hint the interface in whatever writes compressed blobs (cache values, queue payloads, archived log lines). Swapping the implementation in a test then costs one container binding instead of a code change.
Constructor injection only. The interface is the dependency; the algorithm is configuration.
final class ArchiveWriter
{
public function __construct(private CompressInterface $compress) {}
public function write(string $path, string $payload): void
{
file_put_contents($path, $this->compress->compress($payload));
}
}
Algorithm selection via a registry, not via conditionals. When you support more than one codec, keep a map of name to implementation and resolve from config. Store the chosen name next to the payload so reads are not guesses.
final class CompressorRegistry
{
/** @param array<string, CompressInterface> $compressors */
public function __construct(private array $compressors) {}
public function for(?string $name): CompressInterface
{
return $this->compressors[$name ?? 'gzip']
?? throw new InvalidArgumentException("Unknown compressor [{$name}]");
}
}
Bind the map in the provider from config('compress.driver'), and keep the default in config/compress.php rather than in code.
Decorators are the cheapest extension point. Because it is a one-method interface, wrapping is trivial and keeps cross-cutting policy out of the algorithm class.
SkipSmallPayloads: pass through below a byte threshold, since gzip on a 40-byte string makes it larger. Make sure the read side knows which branch was taken, otherwise you will try to gunzip a plain string.NullCompress: a fake for tests and a safe default when the driver is set to none.LoggingCompress: measure input/output size and log the ratio. This is where you will actually learn your compression is useless on JSON full of short keys.ChainCompress: try candidates and keep the shortest result, storing the winner's name.Round trips are your job. The contract is one-directional. Pair it with a decompress contract in the same application namespace, or define a Codec value object that owns both directions plus the algorithm identifier. Do not leak a bare CompressInterface into domain code that also needs to read the data back.
HTTP and storage boundaries. For response compression use your web server, not this package. Where this earns its keep: serialized cache payloads, job payloads, and archived files. Wrap the Laravel cache repository rather than every call site.
final class CompressingCacheRepository implements Repository
{
public function __construct(private Repository $inner, private CompressInterface $compress) {}
public function put($key, $value, $ttl = null): bool
{
return $this->inner->put($key, base64_encode($this->compress->compress(serialize($value))), $ttl);
}
}
Tests: fakes and round-trip properties. Use NullCompress or a spy that records calls; assert that domain code delegates, not that gzip works. Then one focused test per implementation iterates representative payloads (empty string, 1 byte, 1 MB of random bytes, UTF-8 multibyte) and asserts decompress(compress($x)) === $x.
src/ is the spec.compress() can fail, it will surface as false, an empty string, or a \Error from the underlying function depending on the implementation. Normalise at your implementation boundary into a domain exception; do not make callers guess.base64_encode before it touches a JSON column, a cache key, or a URL, and budget the 33 percent expansion when comparing compressed size.compress('') returns '' or a valid empty gzip stream, and test it. Silent ambiguity here turns into corrupt reads months later.SkipSmallPayloads returns the input unchanged, the reader cannot tell compressed from plain. Prefix with a one-byte flag, or store the algorithm alongside the payload. Do this at the storage boundary, never inside the codec.App\Support\Compress\CompressInterface with the same shape and alias the package interface to it, so the external type is confined to one binding line and can be dropped in an afternoon.make install, then make and make contrib before returning. Tests extend WyriHaximus\TestUtilities\TestCase (or AsyncTestCase), require 100 percent coverage, 100 percent MSI via Infection, 100 percent type coverage, and dataproviders wherever possible. Use make unit-testing-filter for single tests, make composer-require "vendor/pkg" for new dependencies, and make help to discover targets. Never cd (the Makefile assumes repo root), never sudo, never docker directly.Makefile or AGENTS.md here. The instructions live in wyrihaximus/makefiles; propose changes there and pull them in with make install.composer show -t to see whether something else is already pulling a compress abstraction into the tree that you should reuse instead.How can I help you explore Laravel packages today?