API responses¶
CBOR has a media type of its own, application/cbor, and an answer in it is built the same way as any
other document: a map, encoded, written out.
use CBOR\MapObject;
use CBOR\NegativeIntegerObject;
use CBOR\TextStringObject;
use CBOR\UnsignedIntegerObject;
use InvalidArgumentException;
function encodeToCBOR(array $data): string
{
$map = MapObject::create();
foreach ($data as $key => $value) {
$keyObject = TextStringObject::create((string) $key);
$valueObject = match (gettype($value)) {
'string' => TextStringObject::create($value),
'integer' => $value >= 0
? UnsignedIntegerObject::create($value)
: NegativeIntegerObject::create($value),
'array' => convertArrayToList($value),
default => throw new InvalidArgumentException(sprintf('Unsupported type "%s"', gettype($value))),
};
$map->add($keyObject, $valueObject);
}
return (string) $map;
}
$response = encodeToCBOR([
'status' => 'success',
'code' => 200,
'data' => ['item1', 'item2'],
]);
header('Content-Type: application/cbor');
echo $response;
A converter like this one is a decision about your protocol, not a feature of the format: what types you accept, what you do with a float, whether a date becomes a tagged instant or a string. Write the mapping down and keep both ends to it — which is the whole difference between a binary protocol and a guess.
The way back, for a request body:
use CBOR\Decoder;
use CBOR\StringStream;
// 32 levels of nesting is plenty for a request body
$decoder = Decoder::create(null, null, 32);
try {
$document = $decoder->decode(StringStream::create($requestBody));
} catch (InvalidArgumentException) {
http_response_code(400);
exit;
}
$data = $document->normalize();
The depth limit and the try block are not optional on a public endpoint: see
the cost of a hostile document.