This document discusses the methods how compound structures (documents, arrays, objects) are persisted through the drivers. And how they are brought back into PHP land.
If an array is a packed array — i.e. the keys start at 0 and are sequential without gaps: BSON array.
If the array is not packed — i.e. having associative (string) keys, the keys don't start at 0, or when there are gaps:: BSON object
A top-level (root) document, always serializes as a BSON document.
These serialize as a BSON array:
[ 8, 5, 2, 3 ] => [ 8, 5, 2, 3 ] [ 0 => 4, 1 => 9 ] => [ 4, 9 ]
These serialize as a BSON document:
[ 0 => 1, 2 => 8, 3 => 12 ] => { "0" : 1, "2" : 8, "3" : 12 } [ "foo" => 42 ] => { "foo" : 42 } [ 1 => 9, 0 => 10 ] => { "1" : 9, "0" : 10 }
Note that the five examples are extracts of a full document, and represent only one value inside a document.
If an object is of the stdClass, serialize as a BSON document.
If an object is a supported class that implements MongoDB\BSON\Type
, then
use the BSON serialization logic for that specific type. MongoDB\BSON\Type
instances (excluding MongoDB\BSON\Serializable
) may only be serialized as
a document field value. Attempting to serialize such an object as a root
document will throw a MongoDB\Driver\Exception\UnexpectedValueException
.
If an object is of an unknown class implementing the MongoDB\BSON\Type
interface, then throw a MongoDB\Driver\Exception\UnexpectedValueException
.
If an object is of any other class, without implementing any special interface, serialize as a BSON document. Keep only public properties, and ignore protected and private properties.
If an object is of a class that implements the MongoDB\BSON\Serializable
interface, call bsonSerialize
and use the returned array or stdClass
to
serialize as a BSON document or array. The BSON type will be determined by the
following:
- Root documents must be serialized as a BSON document.
MongoDB\BSON\Persistable
objects must be serialized as a BSON document.- If
bsonSerialize
returns a packed array, serialize as a BSON array. - If
bsonSerialize
returns a non-packed array orstdClass
, serialize as a BSON document. - If
bsonSerialize
did not return an array orstdClass
, throw anMongoDB\Driver\Exception\UnexpectedValueException
exception.
If an object is of a class that implements the MongoDB\BSON\Persistable
interface (which implies MongoDB\BSON\Serializable
), obtain the properties
in a similar way as in the previous paragraphs, but also add an additional
property __pclass
as a Binary value, with subtype 0x80
and data bearing
the fully qualified class name of the object that is being serialized.
The __pclass
property is added to the array or object returned by
bsonSerialize
, which means it will overwrite any __pclass
key/property
in the bsonSerialize
return value. If you want to avoid this behaviour and
set your own __pclass
value, you must not implement
MongoDB\BSON\Persistable
and should instead implement
MongoDB\BSON\Serializable
directly.
stdClass { public $foo = 42; } => { "foo" : 42 } MyClass { public $foo = 42; protected $prot = "wine"; private $fpr = "cheese"; } => { "foo" : 42 } AnotherClass1 implements MongoDB\BSON\Serializable { public $foo = 42; protected $prot = "wine"; private $fpr = "cheese"; function bsonSerialize() { return [ 'foo' => $this->foo, 'prot' => $this->prot ]; } } => { "foo" : 42, "prot" : "wine" } AnotherClass2 implements MongoDB\BSON\Serializable { public $foo = 42; function bsonSerialize() { return $this; } } => MongoDB\Driver\Exception\UnexpectedValueException("bsonSerialize() did not return an array or stdClass") AnotherClass3 implements MongoDB\BSON\Serializable { private $elements = [ 'foo', 'bar' ]; function bsonSerialize() { return $this->elements; } } => { "0" : "foo", "1" : "bar" } ContainerClass implements MongoDB\BSON\Serializable { public $things = AnotherClass4 implements MongoDB\BSON\Serializable { private $elements = [ 0 => 'foo', 2 => 'bar' ]; function bsonSerialize() { return $this->elements; } } function bsonSerialize() { return [ 'things' => $this->things ]; } } => { "things" : { "0" : "foo", "2" : "bar" } } ContainerClass implements MongoDB\BSON\Serializable { public $things = AnotherClass5 implements MongoDB\BSON\Serializable { private $elements = [ 0 => 'foo', 2 => 'bar' ]; function bsonSerialize() { return array_values($this->elements); } } function bsonSerialize() { return [ 'things' => $this->things ]; } } => { "things" : [ "foo", "bar" ] } ContainerClass implements MongoDB\BSON\Serializable { public $things = AnotherClass6 implements MongoDB\BSON\Serializable { private $elements = [ 'foo', 'bar' ]; function bsonSerialize() { return (object) $this->elements; } } function bsonSerialize() { return [ 'things' => $this->things ]; } } => { "things" : { "0" : "foo", "1" : "bar" } } UpperClass implements MongoDB\BSON\Persistable { public $foo = 42; protected $prot = "wine"; private $fpr = "cheese"; function bsonSerialize() { return [ 'foo' => $this->foo, 'prot' => $this->prot ]; } } => { "foo" : 42, "prot" : "wine", "__pclass" : { "$type" : "80", "$binary" : "VXBwZXJDbGFzcw==" } }
For compound types, there are three data types:
root
: refers to the top-level BSON document onlydocument
: refers to embedded BSON documents onlyarray
: refers to a BSON array
Each of those three data types can be mapped against different PHP types. The possible mapping values are:
not set or
NULL
— this is the default.A BSON array will be deserialized as a PHP
array
.A BSON document (root or embedded) without a
__pclass
property [1] becomes a PHPstdClass
object, with each BSON document key set as a publicstdClass
property.A BSON document (root or embedded) with a
__pclass
property [1] becomes a PHP object of the class name as defined by the__pclass
property.If the named class implements the
MongoDB\BSON\Persistable
interface, then the properties of the BSON document, including the__pclass
property, are sent as an associative array to thebsonUnserialize
function to initialise the object's properties.If the named class does not exist or does not implement the
MongoDB\BSON\Persistable
interface,stdClass
will be used and each BSON document key (including__pclass
) will be set as a publicstdClass
property.
"array"
— turns a BSON array or BSON document into a PHP array. There will be no special treatment of a__pclass
property [1], but it may be set as an element in the returned array if it was present in the BSON document."object"
or"stdClass"
— turns a BSON array or BSON document into astdClass
object. There will be no special treatment of a__pclass
property [1], but it may be set as a public property in the returned object if it was present in the BSON document.any other string
— defines the class name that the BSON array or BSON object should be deserialized as. For BSON objects that include__pclass
properties, that class will take priority.If the named class does not exist, is not concrete (i.e. it is abstract or an interface), or does not implement
MongoDB\BSON\Unserializable
, then anMongoDB\Driver\Exception\InvalidArgumentException
exception is thrown.If the BSON object has a
__pclass
property and that class exists and implementsMongoDB\BSON\Persistable
, it will supersede the class provided in the type map.The properties of the BSON document, including the
__pclass
property if it exists, will be sent as an associative array to thebsonUnserialize
function to initialise the object's properties.
For types that the driver has a wrapper class for, it is possible to define a
map from each supported data type to a class of your own, providing it
implements the MongoDB\BSON\TypeWrapper
interface.
The supported data types are Binary, Decimal128, Javascript, MaxKey, MinKey, ObjectID, Regex, Timestamp, and UTCDateTime.
The MongoDB\BSON\TypeWrapper
interface defines two functions:
createFromBSONType()
, a factory method which takes a MongoDB\BSON\Type
argument, and toBSONType()
.
As an example, a wrapped UTCDateTime class, could look like:
class UTCDateTimeWrapper implements \MongoDB\BSON\TypeWrapper { private $intern; public function __construct( \MongoDB\BSON\UTCDateTime $type ) { $this->intern = $type->toDateTime(); } static function createFromBSONType(\MongoDB\BSON\Type $type) { if (! $type instanceof \MongoDB\BSON\UTCDateTime) { throw new UnexpectedValueException; } return new self( $type ); } function toBSONType() { return new \MongoDB\BSON\UTCDateTime( $this->intern ); } }
If the defined class wraps (composes) an original MongoDB\BSON\*
class,
then it SHOULD also implement the accompanying
\MongoDB\BSON\<classname>Interface
interface.
The type interfaces include all the type-specific methods from the
original class, with the exact same arguments and return types. For example,
they will not include the __construct()
and __debugInfo()
methods.
For example, a user-defined UTCDateTimeWrapper
class needs to implement the
MongoDB\BSON\UTCDateTimeInterface
and MongoDB\BSON\TypeWrapper
interfaces.
The toBSONType()
method must return a value that can be serialized to
BSON, e.g. a scalar value, or an object that implements
MongoDB\BSON\Serializable
. If you return an object that implements
MongoDB\BSON\TypeWrapper
, the driver will not call the toBSONType()
method on this new object.
Returning a different type from the original one could be used as a way to migrate Decimal128 values to plain strings, or downgrade them to floating points or integers. Likewise, you could implement a UTCDateTime wrapper that converts dates to a formatted string; however, that would prevent round-tripping of the original date value.
TypeMaps can be set through the setTypeMap()
method on a
MongoDB\Driver\Cursor
object, or the $typeMap
argument of
MongoDB\BSON\toPHP()
(previously, MongoDB\BSON\toArray()
). Each of the
three classes (root
, document
and array
) can be individually set.
Additionally, you can specify the scalar type mappings through a types
element. Each element in that types
array maps a MongoDB data type to
a user-defined class name.
If the named class does not exist, is not concrete (i.e. it is abstract or an
interface), or does not implement MongoDB\BSON\TypeWrapper
, then an
MongoDB\Driver\Exception\InvalidArgumentException
exception is thrown.
These examples use the following classes:
MyClass
, which does not implement any interfaceYourClass
, which implementsMongoDB\BSON\Unserializable
OurClass
, which implementsMongoDB\BSON\Persistable
TheirClass
, which extendsOurClass
The bsonUnserialize()
method of YourClass
, OurClass
, TheirClass
iterate over the array and set the properties without modifications. It
also sets the $unserialized
property to true
:
function bsonUnserialize( array $map ) { foreach ( $map as $k => $value ) { $this->$k = $value; } $this->unserialized = true; }
/* typemap: [] (all defaults) */ { "foo": "yes", "bar" : false } -> stdClass { $foo => 'yes', $bar => false } { "foo": "no", "array" : [ 5, 6 ] } -> stdClass { $foo => 'no', $array => [ 5, 6 ] } { "foo": "no", "obj" : { "embedded" : 3.14 } } -> stdClass { $foo => 'no', $obj => stdClass { $embedded => 3.14 } } { "foo": "yes", "__pclass": "MyClass" } -> stdClass { $foo => 'yes', $__pclass => 'MyClass' } { "foo": "yes", "__pclass": { "$type" : "80", "$binary" : "MyClass" } } -> stdClass { $foo => 'yes', $__pclass => Binary(0x80, 'MyClass') } { "foo": "yes", "__pclass": { "$type" : "80", "$binary" : "YourClass") } -> stdClass { $foo => 'yes', $__pclass => Binary(0x80, 'YourClass') } { "foo": "yes", "__pclass": { "$type" : "80", "$binary" : "OurClass") } -> OurClass { $foo => 'yes', $__pclass => Binary(0x80, 'OurClass'), $unserialized => true } { "foo": "yes", "__pclass": { "$type" : "44", "$binary" : "YourClass") } -> stdClass { $foo => 'yes', $__pclass => Binary(0x44, 'YourClass') }
/* typemap: [ "root" => "MissingClass" ] */ { "foo": "yes" } -> MongoDB\Driver\Exception\InvalidArgumentException("MissingClass does not exist") /* typemap: [ "root" => "MyClass" ] */ { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MyClass" } } -> MongoDB\Driver\Exception\InvalidArgumentException("MyClass does not implement Unserializable interface") /* typemap: [ "root" => "MongoDB\BSON\Unserializable" ] */ { "foo": "yes" } -> MongoDB\Driver\Exception\InvalidArgumentException("Unserializable is not a concrete class") /* typemap: [ "root" => "YourClass" ] */ { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MongoDB\BSON\Unserializable" } } -> YourClass { $foo => "yes", $__pclass => Binary(0x80, "MongoDB\BSON\Unserializable"), $unserialized => true } /* typemap: [ "root" => "YourClass" ] */ { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MyClass" } } -> YourClass { $foo => "yes", $__pclass => Binary(0x80, "MyClass"), $unserialized => true } /* typemap: [ "root" => "YourClass" ] */ { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "OurClass" } } -> OurClass { $foo => "yes", $__pclass => Binary(0x80, "OurClass"), $unserialized => true } /* typemap: [ "root" => "YourClass" ] */ { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "TheirClass" } } -> TheirClass { $foo => "yes", $__pclass => Binary(0x80, "TheirClass"), $unserialized => true } /* typemap: [ "root" => "OurClass" ] */ { foo: "yes", "__pclass" : { "$type": "80", "$binary": "TheirClass" } } -> TheirClass { $foo => "yes", $__pclass => Binary(0x80, "TheirClass"), $unserialized => true }
/* typemap: [ 'root' => 'YourClass' ] */ { foo: "yes", "__pclass" : { "$type": "80", "$binary": "YourClass" } } -> YourClass { $foo => 'yes', $__pclass => Binary(0x80, 'YourClass'), $unserialized => true }
/* typemap: [ 'root' => 'array', 'document' => 'array' ] */ { "foo": "yes", "bar" : false } -> [ "foo" => "yes", "bar" => false ] { "foo": "no", "array" : [ 5, 6 ] } -> [ "foo" => "no", "array" => [ 5, 6 ] ] { "foo": "no", "obj" : { "embedded" : 3.14 } } -> [ "foo" => "no", "obj" => [ "embedded => 3.14 ] ] { "foo": "yes", "__pclass": "MyClass" } -> [ "foo" => "yes", "__pclass" => "MyClass" ] { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "MyClass" } } -> [ "foo" => "yes", "__pclass" => Binary(0x80, "MyClass") ] { "foo": "yes", "__pclass" : { "$type": "80", "$binary": "OurClass" } } -> [ "foo" => "yes", "__pclass" => Binary(0x80, "OurClass") ]
/* typemap: [ 'root' => 'object', 'document' => 'object' ] */ { "foo": "yes", "__pclass": { "$type": "80", "$binary": "MyClass" } } -> stdClass { $foo => "yes", "__pclass" => Binary(0x80, "MyClass") }
/* typemap: [ 'types' => [ 'UTCDateTime' => 'UTCDateTimeWrapper' ] ] */ { "date" : ISODate("2016-07-19T16:49:54") } -> stdClass { $date => UTCDateTimeWrapper(…) } /* typemap: [ 'types' => [ 'UTCDateTime' => 'UTCDateTimeAsUnixTimestamp' ] ] */ { "date" : ISODate("2016-07-19T16:49:54") } -> stdClass { $date => 1468946994 }
- PHPC-248: Allow ->setTypeMap() to set 'array' and 'stdclass'
- PHPC-249: empty array should be serialized as array
- PHPC-260: Allow "object" as an alias of "stdClass" for setTypeMap()
- PHPC-274: zval_to_bson() ignores BSONSerializable interface
- PHPC-275: object_to_bson() should throw exception if bsonSerialize() returns non-array
- PHPC-288: ODS (Object Document Serializer) support and integrations
- PHPC-311: Rename BSON from/toArray() methods to from/toPHP()
- PHPC-315: Support explicit type mapping for top-level documents
- PHPC-318: Cursor type map should apply to top-level document
- PHPC-319: Top level documents should be deserialized as stdClass by default
- PHPC-329: Determine if ODM class should always supersede the type map
- HHVM-55: Implement BSONPeristable interface
- HHVM-56: Implement BSONSerializable interface
- HHVM-57: Implement BSONUnserializable interface
- HHVM-63: Empty array should be serialized as empty array, and empty object should be serialized as empty object
- HHVM-64: Allow ->setTypeMap() to set 'array' and 'stdclass'
- HHVM-67: ODM should only match field of specific name (__pclass)
- HHVM-84: Implement MongoDBBSONSerializable
- HHVM-85: Implement MongoDBBSONUnserializable / MongoDBBSONPersistable
- HHVM-214 Implement interfaces for userland BSON type classes
- PHP-1457: MongoCollection::insert() Non-public properties of objects.
- PHPC-314: Prototype type map syntax for documents within field paths
[1] | (1, 2, 3, 4) A __pclass property is only deemed to exist if there exists a
property with that name, and it is a Binary value, and the
sub-type of the Binary value is 0x80 . If any of these three conditions
is not met, the __pclass property does not exist and should be treated
as any other normal property. |