msgpack.php
A pure PHP implementation of the MessagePack serialization format.
Features
- Fully compliant with the latest MessagePack specification, including bin, str and ext types
- Supports streaming unpacking
- Supports unsigned 64-bit integers handling
- Supports object serialization
- Works with PHP 5.4-7.x and HHVM
- Fully tested
- Relatively fast
Table of contents
Installation
The recommended way to install the library is through Composer:
$ composer require rybakit/msgpack
Usage
Packing
To pack values use the Packer
class:
use MessagePack\Packer;
$packer = new Packer();
...
$packed = $packer->pack($value);
In the example above, the method pack
automatically pack a value depending on its type.
But not all PHP types can be uniquely translated to MessagePack types. For example,
MessagePack format defines map
and array
types, which are represented by a single array
type in PHP. By default, the packer will pack a PHP array as a MessagePack array if it
has sequential numeric keys, starting from 0
and as a MessagePack map otherwise:
$mpArr1 = $packer->pack([1, 2]); // MP array [1, 2]
$mpArr2 = $packer->pack([0 => 1, 1 => 2]); // MP array [1, 2]
$mpMap1 = $packer->pack([0 => 1, 2 => 3]); // MP map {0: 1, 2: 3}
$mpMap2 = $packer->pack([1 => 2, 2 => 3]); // MP map {1: 2, 2: 3}
$mpMap3 = $packer->pack(['foo' => 1, 'bar' => 2]); // MP map {foo: 1, bar: 2}
However, sometimes you need to pack a sequential array as a MessagePack map.
To do this, use the packMap
method:
$mpMap = $packer->packMap([1, 2]); // {0: 1, 1: 2}
Here is a list of all low-level packer methods:
$packer->packNil(); // MP nil
$packer->packBool(true); // MP bool
$packer->packArray([1, 2]); // MP array
$packer->packMap([1, 2]); // MP map
$packer->packExt(new Ext(1, "\xaa")); // MP ext
$packer->packFloat(4.2); // MP float
$packer->packInt(42); // MP int
$packer->packStr('foo'); // MP str
$packer->packBin("\x80"); // MP bin
Check "Custom Types" section below on how to pack arbitrary PHP objects.
Type detection mode
Automatically detecting a MP type of PHP arrays/strings adds some overhead which can be noticed when you pack large (16- and 32-bit) arrays or strings. However, if you know the variable type in advance (for example, you only work with utf-8 strings or/and associative arrays), you can eliminate this overhead by forcing the packer to use the appropriate type, which will save it from running the auto detection routine:
$packer = new Packer(Packer::FORCE_STR);
// or
// $packer->setTypeDetectionMode(Packer::FORCE_STR);
...
$packer->pack([$utf8string1, $utf8string2]);
Available modes are:
Packer::FORCE_STR
Packer::FORCE_BIN
Packer::FORCE_ARR
Packer::FORCE_MAP
Of course, you can combine modes:
// convert PHP strings to MP strings, PHP arrays to MP maps
$packer->setTypeDetectionMode(Packer::FORCE_STR | Packer::FORCE_MAP);
// convert PHP strings to MP binaries, PHP arrays to MP arrays
$packer->setTypeDetectionMode(Packer::FORCE_BIN | Packer::FORCE_ARR);
// this will throw \InvalidArgumentException
$packer->setTypeDetectionMode(Packer::FORCE_STR | Packer::FORCE_BIN);
$packer->setTypeDetectionMode(Packer::FORCE_MAP | Packer::FORCE_ARR);
Unpacking
To unpack data use the BufferUnpacker
class:
use MessagePack\BufferUnpacker;
$unpacker = new BufferUnpacker();
...
$unpacker->reset($data);
$unpacked = $unpacker->unpack();
If the packed data is received in chunks (e.g. when reading from a stream), use the tryUnpack
method, which will try to unpack data and return an array of unpacked data instead of throwing an InsufficientDataException
:
$unpacker->append($chunk1);
$unpackedBlocks = $unpacker->tryUnpack();
$unpacker->append($chunk2);
$unpackedBlocks = $unpacker->tryUnpack();
To save some keystrokes, the library ships with a syntax sugar class Unpacker
, which
is no more than a tiny wrapper around BufferUnpacker
with a single method unpack
:
use MessagePack\Unpacker;
...
$unpacked = (new Unpacker())->unpack($data);
Unsigned 64-bit Integers
The binary MessagePack format has unsigned 64-bit as its largest integer data type,
but PHP does not support such integers. By default, while unpacking uint64
value
the library will throw a IntegerOverflowException
.
You can change this default behavior to unpack uint64
integer to a string:
$unpacker->setIntOverflowMode(BufferUnpacker::INT_AS_STR);
Or to a Gmp
number (make sure that gmp extension is enabled):
$unpacker->setIntOverflowMode(BufferUnpacker::INT_AS_GMP);
Extensions
To define application-specific types use the Ext
class:
use MessagePack\Ext;
use MessagePack\Packer;
use MessagePack\Unpacker;
$packerd = (new Packer())->pack(new Ext(42, "\xaa"));
$ext = (new Unpacker())->unpack($packed);
$extType = $ext->getType(); // 42
$extData = $ext->getData(); // "\xaa"
Custom Types
In addition to the basic types, the library provides the functionality to serialize and deserialize arbitrary types. To do this, you need to create a transformer, that converts your type to a type, which can be handled by MessagePack.
For example, the code below shows how to add DateTime
object support:
use MessagePack\TypeTransformer\TypeTransformer;
class DateTimeTransformer implements TypeTransformer
{
private $id;
public function __construct($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function supports($value)
{
return $value instanceof \DateTime;
}
public function transform($value)
{
return $value->format(\DateTime::RFC3339);
}
public function reverseTransform($data)
{
return new \DateTime($data);
}
}
use MessagePack\BufferUnpacker;
use MessagePack\Packer;
use MessagePack\TypeTransformer\Collection;
$packer = new Packer();
$unpacker = new BufferUnpacker();
$coll = new Collection([new DateTimeTransformer(5)]);
// $coll->add(new AnotherTypeTransformer(42));
$packer->setTransformers($coll);
$unpacker->setTransformers($coll);
$packed = $packer->pack(['foo' => new DateTime(), 'bar' => 'baz']);
$raw = $unpacker->reset($packed)->unpack();
Exceptions
If an error occurs during packing/unpacking, a PackingFailedException
or UnpackingFailedException
will be thrown, respectively.
In addition, there are two more exceptions that can be thrown during unpacking:
InsufficientDataException
IntegerOverflowException
Tests
Run tests as follows:
$ phpunit
Also, if you already have Docker installed, you can run the tests in a docker container. First, create a container:
$ ./dockerfile.sh | docker build -t msgpack -
The command above will create a container named msgpack
with PHP 7.0 runtime.
You may change the default runtime by defining the PHP_RUNTIME
environment variable:
$ PHP_RUNTIME='php:7.1-cli' ./dockerfile.sh | docker build -t msgpack -
See a list of various runtimes here.
Then run the unit tests:
$ docker run --rm --name msgpack -v $(pwd):/msgpack -w /msgpack msgpack
Performance
To check the performance run:
$ php tests/bench.php
This command will output something like:
Filter: MessagePack\Tests\Perf\Filter\ListFilter
Rounds: 3
Iterations: 100000
=============================================
Test/Target Packer BufferUnpacker
---------------------------------------------
nil .................. 0.0091 ........ 0.0223
false ................ 0.0100 ........ 0.0207
true ................. 0.0094 ........ 0.0237
7-bit uint #1 ........ 0.0085 ........ 0.0167
7-bit uint #2 ........ 0.0094 ........ 0.0153
7-bit uint #3 ........ 0.0093 ........ 0.0161
5-bit sint #1 ........ 0.0103 ........ 0.0202
5-bit sint #2 ........ 0.0102 ........ 0.0201
5-bit sint #3 ........ 0.0102 ........ 0.0201
8-bit uint #1 ........ 0.0122 ........ 0.0341
8-bit uint #2 ........ 0.0122 ........ 0.0379
8-bit uint #3 ........ 0.0131 ........ 0.0344
16-bit uint #1 ....... 0.0166 ........ 0.0460
16-bit uint #2 ....... 0.0172 ........ 0.0466
16-bit uint #3 ....... 0.0174 ........ 0.0486
32-bit uint #1 ....... 0.0212 ........ 0.0595
32-bit uint #2 ....... 0.0211 ........ 0.0591
32-bit uint #3 ....... 0.0215 ........ 0.0598
64-bit uint #1 ....... 0.0326 ........ 0.0729
64-bit uint #2 ....... 0.0332 ........ 0.0728
8-bit int #1 ......... 0.0122 ........ 0.0404
8-bit int #2 ......... 0.0128 ........ 0.0407
8-bit int #3 ......... 0.0123 ........ 0.0431
16-bit int #1 ........ 0.0184 ........ 0.0527
16-bit int #2 ........ 0.0185 ........ 0.0514
16-bit int #3 ........ 0.0160 ........ 0.0523
32-bit int #1 ........ 0.0215 ........ 0.0679
32-bit int #2 ........ 0.0207 ........ 0.0681
32-bit int #3 ........ 0.0231 ........ 0.0707
64-bit int #1 ........ 0.0329 ........ 0.0756
64-bit int #2 ........ 0.0336 ........ 0.0780
64-bit int #3 ........ 0.0342 ........ 0.0759
64-bit float #1 ...... 0.0282 ........ 0.0644
64-bit float #2 ...... 0.0284 ........ 0.0618
64-bit float #3 ...... 0.0280 ........ 0.0623
fix string #1 ........ 0.0249 ........ 0.0225
fix string #2 ........ 0.0300 ........ 0.0324
fix string #3 ........ 0.0268 ........ 0.0349
fix string #4 ........ 0.0291 ........ 0.0328
8-bit string #1 ...... 0.0322 ........ 0.0563
8-bit string #2 ...... 0.0370 ........ 0.0568
8-bit string #3 ...... 0.0438 ........ 0.0569
16-bit string #1 ..... 0.0488 ........ 0.0685
16-bit string #2 ..... 3.2064 ........ 0.3242
32-bit string ........ 3.2056 ........ 0.3364
wide char string #1 .. 0.0288 ........ 0.0353
wide char string #2 .. 0.0341 ........ 0.0551
8-bit binary #1 ...... 0.0285 ........ 0.0474
8-bit binary #2 ...... 0.0293 ........ 0.0482
8-bit binary #3 ...... 0.0314 ........ 0.0509
16-bit binary ........ 0.0374 ........ 0.0638
32-bit binary ........ 0.3761 ........ 0.3347
fixext 1 ............. 0.0276 ........ 0.0739
fixext 2 ............. 0.0275 ........ 0.0794
fixext 4 ............. 0.0277 ........ 0.0788
fixext 8 ............. 0.0281 ........ 0.0789
fixext 16 ............ 0.0320 ........ 0.0802
8-bit ext ............ 0.0367 ........ 0.0880
16-bit ext ........... 0.0406 ........ 0.1001
32-bit ext ........... 0.3830 ........ 0.3734
fix array #1 ......... 0.0245 ........ 0.0233
fix array #2 ......... 0.0857 ........ 0.0900
16-bit array #1 ...... 0.2508 ........ 0.2913
16-bit array #2 ........... S ............. S
32-bit array .............. S ............. S
complex array ........ 0.3618 ........ 0.4304
fix map #1 ........... 0.1633 ........ 0.1900
fix map #2 ........... 0.0723 ........ 0.0684
fix map #3 ........... 0.0789 ........ 0.1155
fix map #4 ........... 0.0789 ........ 0.0901
16-bit map #1 ........ 0.4313 ........ 0.5105
16-bit map #2 ............. S ............. S
32-bit map ................ S ............. S
complex map .......... 0.5088 ........ 0.5348
=============================================
Total 10.5549 6.6065
Skipped 4 4
Failed 0 0
Ignored 0 0
You may change default benchmark settings by defining the following environment variables:
MP_BENCH_TARGETS
(pure_p, pure_ps, pure_pa, pure_psa, pure_bu, pecl_p, pecl_u)MP_BENCH_ITERATIONS
/MP_BENCH_DURATION
MP_BENCH_ROUNDS
MP_BENCH_TESTS
For example:
$ export MP_BENCH_TARGETS=pure_p
$ export MP_BENCH_ITERATIONS=1000000
$ export MP_BENCH_ROUNDS=5
$ # a comma separated list of test names
$ export MP_BENCH_TESTS='complex array, complex map'
$ # or an regexp
$ # export MP_BENCH_TESTS='/complex (array|map)/'
$ php tests/bench.php
Another example, benchmarking both the library and msgpack pecl extension:
$ MP_BENCH_TARGETS=pure_ps,pure_bu,pecl_p,pecl_u php tests/bench.php
Filter: MessagePack\Tests\Perf\Filter\ListFilter
Rounds: 3
Iterations: 100000
================================================================================
Test/Target Packer (str) BufferUnpacker msgpack_pack msgpack_unpack
--------------------------------------------------------------------------------
nil ....................... 0.0083 ........ 0.0215 ...... 0.0089 ........ 0.0062
false ..................... 0.0102 ........ 0.0207 ...... 0.0077 ........ 0.0062
true ...................... 0.0086 ........ 0.0214 ...... 0.0076 ........ 0.0064
7-bit uint #1 ............. 0.0119 ........ 0.0164 ...... 0.0090 ........ 0.0065
7-bit uint #2 ............. 0.0094 ........ 0.0163 ...... 0.0083 ........ 0.0053
7-bit uint #3 ............. 0.0096 ........ 0.0159 ...... 0.0082 ........ 0.0061
5-bit sint #1 ............. 0.0103 ........ 0.0196 ...... 0.0081 ........ 0.0061
5-bit sint #2 ............. 0.0103 ........ 0.0199 ...... 0.0082 ........ 0.0069
5-bit sint #3 ............. 0.0104 ........ 0.0198 ...... 0.0080 ........ 0.0075
8-bit uint #1 ............. 0.0124 ........ 0.0342 ...... 0.0078 ........ 0.0067
8-bit uint #2 ............. 0.0125 ........ 0.0357 ...... 0.0082 ........ 0.0078
8-bit uint #3 ............. 0.0127 ........ 0.0354 ...... 0.0081 ........ 0.0077
16-bit uint #1 ............ 0.0196 ........ 0.0469 ...... 0.0096 ........ 0.0070
16-bit uint #2 ............ 0.0171 ........ 0.0458 ...... 0.0083 ........ 0.0067
16-bit uint #3 ............ 0.0186 ........ 0.0469 ...... 0.0082 ........ 0.0065
32-bit uint #1 ............ 0.0215 ........ 0.0600 ...... 0.0083 ........ 0.0068
32-bit uint #2 ............ 0.0211 ........ 0.0590 ...... 0.0082 ........ 0.0065
32-bit uint #3 ............ 0.0213 ........ 0.0586 ...... 0.0088 ........ 0.0073
64-bit uint #1 ............ 0.0327 ........ 0.0747 ...... 0.0095 ........ 0.0061
64-bit uint #2 ............ 0.0312 ........ 0.0714 ...... 0.0082 ........ 0.0065
8-bit int #1 .............. 0.0123 ........ 0.0413 ...... 0.0089 ........ 0.0065
8-bit int #2 .............. 0.0124 ........ 0.0401 ...... 0.0091 ........ 0.0066
8-bit int #3 .............. 0.0123 ........ 0.0402 ...... 0.0081 ........ 0.0066
16-bit int #1 ............. 0.0182 ........ 0.0502 ...... 0.0080 ........ 0.0068
16-bit int #2 ............. 0.0169 ........ 0.0523 ...... 0.0082 ........ 0.0067
16-bit int #3 ............. 0.0173 ........ 0.0504 ...... 0.0081 ........ 0.0066
32-bit int #1 ............. 0.0211 ........ 0.0691 ...... 0.0092 ........ 0.0063
32-bit int #2 ............. 0.0210 ........ 0.0690 ...... 0.0087 ........ 0.0068
32-bit int #3 ............. 0.0210 ........ 0.0696 ...... 0.0082 ........ 0.0067
64-bit int #1 ............. 0.0317 ........ 0.0736 ...... 0.0083 ........ 0.0064
64-bit int #2 ............. 0.0318 ........ 0.0762 ...... 0.0082 ........ 0.0078
64-bit int #3 ............. 0.0321 ........ 0.0765 ...... 0.0091 ........ 0.0078
64-bit float #1 ........... 0.0276 ........ 0.0620 ...... 0.0077 ........ 0.0065
64-bit float #2 ........... 0.0292 ........ 0.0659 ...... 0.0083 ........ 0.0065
64-bit float #3 ........... 0.0294 ........ 0.0672 ...... 0.0069 ........ 0.0071
fix string #1 ............. 0.0157 ........ 0.0210 ...... 0.0085 ........ 0.0063
fix string #2 ............. 0.0178 ........ 0.0348 ...... 0.0100 ........ 0.0080
fix string #3 ............. 0.0183 ........ 0.0351 ...... 0.0085 ........ 0.0090
fix string #4 ............. 0.0175 ........ 0.0335 ...... 0.0084 ........ 0.0081
8-bit string #1 ........... 0.0200 ........ 0.0579 ...... 0.0083 ........ 0.0092
8-bit string #2 ........... 0.0205 ........ 0.0605 ...... 0.0089 ........ 0.0079
8-bit string #3 ........... 0.0199 ........ 0.0600 ...... 0.0132 ........ 0.0084
16-bit string #1 .......... 0.0256 ........ 0.0709 ...... 0.0132 ........ 0.0090
16-bit string #2 .......... 0.3552 ........ 0.3236 ...... 0.3384 ........ 0.2617
32-bit string ............. 0.3617 ........ 0.3382 ...... 0.3358 ........ 0.2717
wide char string #1 ....... 0.0183 ........ 0.0334 ...... 0.0084 ........ 0.0080
wide char string #2 ....... 0.0201 ........ 0.0582 ...... 0.0087 ........ 0.0095
8-bit binary #1 ................ I ............. I ........... F ............. I
8-bit binary #2 ................ I ............. I ........... F ............. I
8-bit binary #3 ................ I ............. I ........... F ............. I
16-bit binary .................. I ............. I ........... F ............. I
32-bit binary .................. I ............. I ........... F ............. I
fixext 1 ....................... I ............. I ........... F ............. F
fixext 2 ....................... I ............. I ........... F ............. F
fixext 4 ....................... I ............. I ........... F ............. F
fixext 8 ....................... I ............. I ........... F ............. F
fixext 16 ...................... I ............. I ........... F ............. F
8-bit ext ...................... I ............. I ........... F ............. F
16-bit ext ..................... I ............. I ........... F ............. F
32-bit ext ..................... I ............. I ........... F ............. F
fix array #1 .............. 0.0251 ........ 0.0243 ...... 0.0158 ........ 0.0080
fix array #2 .............. 0.0774 ........ 0.0893 ...... 0.0209 ........ 0.0209
16-bit array #1 ........... 0.2440 ........ 0.2675 ...... 0.0402 ........ 0.0498
16-bit array #2 ................ S ............. S ........... S ............. S
32-bit array ................... S ............. S ........... S ............. S
complex array .................. I ............. I ........... F ............. F
fix map #1 ..................... I ............. I ........... F ............. I
fix map #2 ................ 0.0598 ........ 0.0675 ...... 0.0182 ........ 0.0191
fix map #3 ..................... I ............. I ........... F ............. I
fix map #4 ..................... I ............. I ........... F ............. I
16-bit map #1 ............. 0.4302 ........ 0.4706 ...... 0.0399 ........ 0.0679
16-bit map #2 .................. S ............. S ........... S ............. S
32-bit map ..................... S ............. S ........... S ............. S
complex map ............... 0.4706 ........ 0.5210 ...... 0.0702 ........ 0.0755
================================================================================
Total 2.8615 4.2113 1.2680 1.0922
Skipped 4 4 4 4
Failed 0 0 17 9
Ignored 17 17 0 8
Note, that this is not a fair comparison as the msgpack extension (0.5.2+, 2.0) doesn't support ext, bin and utf-8 str types.
License
The library is released under the MIT License. See the bundled LICENSE file for details.