spomky-labs/cbor-php
RFC 8949 CBOR encoder/decoder for PHP 8+. Supports all major types, tags, indefinite-length items, and streaming decode for low memory. Type-safe API with normalization to native PHP types; extensible custom tags.
Installation:
composer require spomky-labs/cbor-php
Ensure ext-mbstring and brick/math are installed (GMP/BCMath recommended for large integers).
First Use Case: Encode a simple PHP array to CBOR and decode it back:
use CBOR\Encoder;
use CBOR\Decoder;
use CBOR\StringStream;
// Encode
$encoder = Encoder::create();
$cborData = $encoder->encode(['name' => 'Alice', 'age' => 30]);
$binary = (string) $cborData;
// Decode
$decoder = Decoder::create();
$decoded = $decoder->decode(StringStream::create($binary));
$phpData = $decoded->normalize(); // ['name' => 'Alice', 'age' => 30]
Key Entry Points:
CBOR\Encoder::create()CBOR\Decoder::create()CBOR\StreamingDecoder::create() (for large payloads)$object->normalize() (converts CBOR objects to native PHP types).Where to Look First:
$encoder = Encoder::create();
$cbor = $encoder->encode([
'user' => ['name' => 'Bob', 'active' => true],
'metadata' => ['timestamp' => time()]
]);
MapObject/ListObject for fine-grained control:
use CBOR\MapObject;
use CBOR\TextStringObject;
use CBOR\UnsignedIntegerObject;
$map = MapObject::create()
->add(TextStringObject::create('key'), UnsignedIntegerObject::create(123));
$binary = (string) $map;
$decoder = Decoder::create();
$decoded = $decoder->decode(StringStream::create($binary));
$phpData = $decoded->normalize();
$stream = fopen('large.cbor', 'r');
$decoder = StreamingDecoder::create();
foreach ($decoder->decode($stream) as $item) {
$phpItem = $item->normalize();
// Process item
}
use CBOR\Tag\TimestampTag;
use CBOR\UnsignedIntegerObject;
$timestamp = TimestampTag::create(UnsignedIntegerObject::create(time()));
$dateTime = $timestamp->normalize(); // DateTimeImmutable
AbstractTag (see Custom Tags Guide):
class MyCustomTag extends AbstractTag {
public function normalize(): mixed { /* ... */ }
}
// Decode CBOR request payload
$decoder = Decoder::create();
$data = $decoder->decode(request()->getContent())->normalize();
// Encode CBOR response
return response((string) Encoder::create()->encode($data));
use Illuminate\Database\Eloquent\Model;
use CBOR\Encoder;
class User extends Model {
public function toCbor(): string {
return (string) Encoder::create()->encode($this->toArray());
}
}
$cborData = (string) Encoder::create()->encode($data);
cache()->put('key', $cborData, now()->addHours(1));
try {
$decoder = Decoder::create();
$decoded = $decoder->decode($stream);
} catch (CBOR\Exception\DecodingException $e) {
Log::error('CBOR decode error: ' . $e->getMessage());
abort(400, 'Invalid CBOR data');
}
Normalizable to enforce type constraints:
$data = $decoded->normalize();
if (!is_array($data) || !isset($data['name'])) {
throw new \InvalidArgumentException('Invalid CBOR structure');
}
StringStream for in-memory data (avoids file I/O overhead).StreamingDecoder for large payloads (e.g., log files, database dumps).brick/math with GMP/BCMath for large integers/decimals:
// In composer.json
"require": {
"ext-gmp": "*"
}
Register a singleton for global access:
// app/Providers/CborServiceProvider.php
namespace App\Providers;
use CBOR\Encoder;
use CBOR\Decoder;
use Illuminate\Support\ServiceProvider;
class CborServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton(Encoder::class, function () {
return Encoder::create();
});
$this->app->singleton(Decoder::class, function () {
return Decoder::create();
});
}
}
// app/Http/Middleware/DecodeCborRequest.php
namespace App\Http\Middleware;
use CBOR\Decoder;
use Closure;
class DecodeCborRequest {
public function handle($request, Closure $next) {
if ($request->isCbor()) { // Custom helper
$decoder = app(Decoder::class);
$data = $decoder->decode($request->getContent())->normalize();
$request->merge(['cbor' => $data]);
}
return $next($request);
}
}
use CBOR\Encoder;
use PHPUnit\Framework\TestCase;
class CborTest extends TestCase {
public function testEncoding() {
$encoder = Encoder::create();
$cbor = $encoder->encode(['test' => 123]);
$this->assertIsString($cbor);
}
}
$response = $this->post('/api/data', $cborBinary, [
'headers' => ['Content-Type' => 'application/cbor']
]);
$response->assertOk();
LONGBLOB in MySQL):
// Migration
Schema::create('cbor_data', function (Blueprint $table) {
$table->id();
$table->binary('payload');
$table->timestamps();
});
// Model
class CborData extends Model {
protected $casts = [
'payload' => 'array', // Automatically decode on retrieval
];
public function getPayloadAttribute($value) {
if ($value) {
$decoder = app(Decoder::class);
return $decoder->decode($value)->normalize();
}
return null;
}
public function setPayloadAttribute($value) {
$encoder = app(Encoder::class);
$this->attributes['payload'] = (string) $encoder->encode($value);
}
}
"30" instead of 30).
$data = ['age' => UnsignedIntegerObject::create(30)]->normalize();
// $data['age'] is a string, not an integer.
How can I help you explore Laravel packages today?