nullpunkt/lz-string-php
LZ-string compression and decompression for PHP, letting you store and share compressed strings in URLs or localStorage with full compatibility with the JavaScript lz-string library.
Install the package and confirm the class that your tagged release exposes. Current releases ship a static class at Nullpunkt\LZString\LZString; older tags may expose a global LZString or require an instance, but the method names are the same.
composer require nullpunkt/lz-string-php
use Nullpunkt\LZString\LZString;
$compressed = LZString::compressToBase64('Hello world');
$decompressed = LZString::decompressFromBase64($compressed);
var_dump($compressed, $decompressed);
Look first at:
vendor/nullpunkt/lz-string-php/src/LZString.php — confirm the namespace, whether methods are static, and which compress*/decompress* variants exist.lz-string package.tests/ directory — fixture outputs are the fastest way to prove PHP and JS agree.The first real use case is almost always browser ↔ server interoperability: the frontend uses lz-string to shrink JSON before putting it in localStorage, a URL fragment, or a request body, and Laravel needs to read it back. Start with one round-trip fixture generated by Node:
// node
const LZString = require('lz-string');
console.log(LZString.compressToBase64('Hello world'));
Then assert the exact same string in a PHPUnit test before building anything on top of it.
Use a small middleware or a FormRequest preparer when the browser sends a compressed blob instead of JSON.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Nullpunkt\LZString\LZString;
class DecompressLzStringBody
{
public function handle(Request $request, Closure $next)
{
$raw = $request->getContent();
if ($raw !== '' && ! str_starts_with($raw, '{')) {
$json = LZString::decompressFromBase64($raw);
$data = is_string($json) ? json_decode($json, true) : null;
if (is_array($data)) {
$request->replace($data);
}
}
return $next($request);
}
}
Register it only on the routes that need it; do not run decompression on every request.
Useful for large, repetitive JSON payloads where the client already has lz-string.
$json = $payload->toJson();
return response(LZString::compressToBase64($json))
->header('Content-Type', 'text/plain; charset=UTF-8')
->header('X-Content-Encoding', 'lz-string-base64');
const res = await fetch('/api/filters');
const data = JSON.parse(LZString.decompressFromBase64(await res.text()));
Be explicit about the encoding with a custom header; do not pretend it is ordinary JSON.
A cast keeps controllers clean and makes the compression format an implementation detail.
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Nullpunkt\LZString\LZString;
class LzStringJson implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
if ($value === null || $value === '') {
return null;
}
$json = LZString::decompressFromBase64($value);
return is_string($json) ? json_decode($json, true) : null;
}
public function set($model, string $key, $value, array $attributes)
{
return [
$key => $value === null
? null
: LZString::compressToBase64(json_encode($value)),
];
}
}
Store the Base64 form in TEXT/LONGTEXT, not the raw binary/UTF-16 form.
For shareable UI state (filters, dashboard layout), use the URI-safe codec and still encode it when embedding in a query string:
$state = LZString::compressToEncodedURIComponent(json_encode($filters));
$url = '/dashboard?state=' . rawurlencode($state);
$filters = json_decode(
LZString::decompressFromEncodedURIComponent($request->query('state')),
true
);
A URL fragment (#state=...) avoids sending the payload to the server entirely and is a good fit for purely client-side state.
Because this is a static utility with no Laravel service provider, wrap it for testability and to isolate the dependency:
namespace App\Support;
use Nullpunkt\LZString\LZString;
class Compressor
{
public function compress(string $value): string
{
return LZString::compressToBase64($value);
}
public function decompress(string $value): ?string
{
$result = LZString::decompressFromBase64($value);
return is_string($result) ? $result : null;
}
}
This lets you mock compression in tests and swap implementations later without touching call sites.
Treat the JS library as the source of truth. Generate fixtures once, commit them, and test both directions:
public function test_matches_javascript_fixtures(): void
{
$this->assertSame('BYUwNmD2AEDukCcoA', LZString::compressToBase64('Hello world'));
$this->assertSame('Hello world', LZString::decompressFromBase64('BYUwNmD2AEDukCcoA'));
}
Include at least one non-ASCII fixture and one emoji fixture.
compressToBase64() can emit +, /, and =. In a query string, + is interpreted as a space. Use compressToEncodedURIComponent() for URLs and rawurlencode() the result anyway.compressToUTF16() may produce bytes that are not valid UTF-8 (including null bytes), which will corrupt in MySQL utf8mb4 columns, cookies, or log lines. Prefer compressToBase64() for anything persisted or transmitted; use compressToUTF16() only when you specifically need to match the browser’s localStorage representation."héllo 😀" catches this immediately.null, false, an empty string, or emit PHP warnings for undefined offsets. Always validate the decoded result and json_decode() return value before trusting it. Wrap calls in try/catch for PHP 8 warning-to-exception configurations.post_max_size, middleware), cap decompressed length, and validate the decoded JSON before use. Do not decompress arbitrary user input in a loop.gzcompress()/gzdecode(). Benchmark with realistic data; if there is no JavaScript interoperability requirement, native zlib is the better default.null or '' depending on version. Normalize both cases in your wrapper rather than scattering null checks.composer.json, check the package’s PHP constraint, and run your test suite on your target PHP version (especially PHP 8.1+). Add a fixture test so a future composer update cannot silently change output.lz-string in package.json and keep it in sync with the PHP port. The compressed format is stable, but a major frontend upgrade is a good moment to rerun the fixture tests.LZString:: directly throughout the codebase.How can I help you explore Laravel packages today?