|
| 1 | +# Object serialization |
| 2 | + |
| 3 | +PHongo exports 4 public interfaces to facilitate serializing PHP classes to and from BSON. |
| 4 | + |
| 5 | +- BSON\Type |
| 6 | + * (no methods) |
| 7 | +- BSON\Serializable |
| 8 | + * abstract public method bsonSerialize(); |
| 9 | +- BSON\Unserializable |
| 10 | + * abstract public method bsonUnserialize(array $data); |
| 11 | +- BSON\Persistable |
| 12 | + * implements both BSON\Serializable and BSON\Unserializable |
| 13 | + |
| 14 | + |
| 15 | +Objects that implement the BSON\Type interface get very special treatment by the BSON serializer. In |
| 16 | +general, these objects represent a BSON type that cannot be natively represented in PHP - such as |
| 17 | +BSON\UTCDatetime and BSON\ObjectID - and are specifically checked for and handled. |
| 18 | +Note that implmenting BSON\Type and or BSON\Unserializable standalone does not do anything for |
| 19 | +userland classes. |
| 20 | + |
| 21 | +Since BSON\Type is a public interface, normal userland classes can indeed implement it to, and apply |
| 22 | +their own custom rules on the serialization. |
| 23 | +To achieve this though, the BSON\Serializable interface should be implemented instead, providing the |
| 24 | +bsonSerialize() function which should return a PHP array() representing the document that should be |
| 25 | +stored. |
| 26 | +Furthermore, if the object implements the BSON\Persistable interface, the driver will embed a BSON |
| 27 | +Binary (0x80) with the fully qualified classname, into the document. |
| 28 | + |
| 29 | +During unserialization of a document, if a BSON Binary (of type 0x80) is encountered, the driver |
| 30 | +will peak at the value and attempt to resolve it to a classname (triggering autoloaders if |
| 31 | +neccesary). If the classname cannot be resolved, no harm no faul, and nothing magical happens. |
| 32 | +However, if the class exists, the driver will call the bsonUnserialize() method of the detected |
| 33 | +class, passing it the full document. |
| 34 | +Note that this happens depth-first, meaning any subdocuments (relations) are resolved first, and |
| 35 | +then crawled upwards to the start of the document. |
| 36 | + |
| 37 | + |
| 38 | +## Examples |
| 39 | + |
| 40 | +Imagine the following two classes, Person and Address. |
| 41 | + |
| 42 | +A Person has name, age, friends (Person), and addresses (Address). |
| 43 | +A Person also has a secret, that we would rather not store in a database. |
| 44 | + |
| 45 | +``` |
| 46 | +<?php |
| 47 | +class Person implements BSON\Persistable { |
| 48 | + protected $_id; |
| 49 | + protected $name; |
| 50 | + protected $age; |
| 51 | + protected $address = array(); |
| 52 | + protected $friends = array(); |
| 53 | + protected $secret = "none"; |
| 54 | +
|
| 55 | + function __construct($name, $age) { |
| 56 | + $this->name = $name; |
| 57 | + $this->age = $age; |
| 58 | + $this->address = array(); |
| 59 | + $this->secret = "$name confidential info"; |
| 60 | + /* Pregenerate our ObjectID */ |
| 61 | + $this->_id = new BSON\ObjectID(); |
| 62 | + } |
| 63 | + function addAddress(Address $address) { |
| 64 | + $this->address[] = $address; |
| 65 | + } |
| 66 | + function addFriend(Person $friend) { |
| 67 | + $this->friends[] = $friend; |
| 68 | + } |
| 69 | + function bsonSerialize() { |
| 70 | + return array( |
| 71 | + "_id" => $this->_id, |
| 72 | + "name" => $this->name, |
| 73 | + "age" => $this->age, |
| 74 | + "address" => $this->address, |
| 75 | + "friends" => $this->friends, |
| 76 | + ); |
| 77 | + } |
| 78 | + function bsonUnserialize(array $data) { |
| 79 | + $this->_id = $data["_id"]; |
| 80 | + $this->name = $data["name"]; |
| 81 | + $this->age = $data["age"]; |
| 82 | + $this->address = $data["address"]; |
| 83 | + $this->friends = $data["friends"]; |
| 84 | + } |
| 85 | +} |
| 86 | +
|
| 87 | +class Address implements BSON\Persistable { |
| 88 | + protected $zip; |
| 89 | + protected $country; |
| 90 | +
|
| 91 | + function __construct($zip, $country) { |
| 92 | + $this->zip = $zip; |
| 93 | + $this->country = $country; |
| 94 | + } |
| 95 | + function bsonSerialize() { |
| 96 | + return array( |
| 97 | + "zip" => $this->zip, |
| 98 | + "country" => $this->country, |
| 99 | + ); |
| 100 | + } |
| 101 | + function bsonUnserialize(array $data) { |
| 102 | + $this->zip = $data["zip"]; |
| 103 | + $this->country = $data["country"]; |
| 104 | + } |
| 105 | +} |
| 106 | +?> |
| 107 | +``` |
| 108 | + |
| 109 | +Hannes, 31 years old, has two addresses; |
| 110 | + |
| 111 | +- Sunnyvale, USA |
| 112 | +- Kopavogur, Iceland |
| 113 | + |
| 114 | +He is friends with young Jeremy, 21 year old who lives in Michigan |
| 115 | + |
| 116 | +``` |
| 117 | +<?php |
| 118 | +$hannes = new Person("Hannes", 31); |
| 119 | +$sunnyvale = new Address(94086, "USA"); |
| 120 | +$kopavogur = new Address(200, "Iceland"); |
| 121 | +$hannes->addAddress($sunnyvale); |
| 122 | +$hannes->addAddress($kopavogur); |
| 123 | +
|
| 124 | +$mikola = new Person("Jeremy", 21); |
| 125 | +$michigan = new Address(48169, "USA"); |
| 126 | +$mikola->addAddress($michigan); |
| 127 | +
|
| 128 | +$hannes->addFriend($mikola); |
| 129 | +?> |
| 130 | +``` |
| 131 | + |
| 132 | + |
| 133 | +To save this information we can insert $hannes to the database like so: |
| 134 | + |
| 135 | +``` |
| 136 | +<?php |
| 137 | +try { |
| 138 | + $wc = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY); |
| 139 | + $manager = new MongoDB\Driver\Manager("mongodb://192.168.112.10:2000"); |
| 140 | + $result = $manager->executeInsert("congress.people", $hannes, $wc); |
| 141 | + echo "Hannes has been inserted\n"; |
| 142 | +} catch(Exception $e) { |
| 143 | + echo $e->getMessage(), "\n"; |
| 144 | + exit; |
| 145 | +} |
| 146 | +?> |
| 147 | +``` |
| 148 | + |
| 149 | +This will result in the following document to be stored (as shown by mongo shell): |
| 150 | + |
| 151 | +``` |
| 152 | +{ |
| 153 | + "_id" : ObjectId("54c9664fbd21b9416f2e0501"), |
| 154 | + "__" : BinData(128,"UGVyc29u"), |
| 155 | + "name" : "Hannes", |
| 156 | + "age" : 31, |
| 157 | + "address" : [ |
| 158 | + [ |
| 159 | + BinData(128,"QWRkcmVzcw=="), |
| 160 | + 94086, |
| 161 | + "USA" |
| 162 | + ], |
| 163 | + [ |
| 164 | + BinData(128,"QWRkcmVzcw=="), |
| 165 | + 200, |
| 166 | + "Iceland" |
| 167 | + ] |
| 168 | + ], |
| 169 | + "friends" : [ |
| 170 | + [ |
| 171 | + BinData(128,"UGVyc29u"), |
| 172 | + ObjectId("54c9664fbd21b9416f2e0502"), |
| 173 | + "Jeremy", |
| 174 | + 21, |
| 175 | + [ |
| 176 | + [ |
| 177 | + BinData(128,"QWRkcmVzcw=="), |
| 178 | + 48169, |
| 179 | + "USA" |
| 180 | + ] |
| 181 | + ], |
| 182 | + { |
| 183 | + |
| 184 | + } |
| 185 | + ] |
| 186 | + ] |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +When we read this document back, the driver will convert the individual subdocuments back into the |
| 191 | +original classes, as we have full type information for them now: |
| 192 | + |
| 193 | +``` |
| 194 | +<?php |
| 195 | +$rp = new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRIMARY_PREFERRED); |
| 196 | +$query = new MongoDB\Driver\Query(array()); |
| 197 | +$cursor = $manager->executeQuery("congress.people", $query, $rp); |
| 198 | +foreach($cursor as $person) { |
| 199 | + var_dump($person); |
| 200 | +} |
| 201 | +?> |
| 202 | +``` |
| 203 | + |
| 204 | +Which results in: |
| 205 | + |
| 206 | +``` |
| 207 | +object(Person)#22 (6) { |
| 208 | + ["_id":protected]=> |
| 209 | + object(BSON\ObjectID)#15 (0) { |
| 210 | + } |
| 211 | + ["name":protected]=> |
| 212 | + string(6) "Hannes" |
| 213 | + ["age":protected]=> |
| 214 | + int(31) |
| 215 | + ["address":protected]=> |
| 216 | + array(2) { |
| 217 | + [0]=> |
| 218 | + object(Address)#16 (2) { |
| 219 | + ["zip":protected]=> |
| 220 | + int(94086) |
| 221 | + ["country":protected]=> |
| 222 | + string(3) "USA" |
| 223 | + } |
| 224 | + [1]=> |
| 225 | + object(Address)#17 (2) { |
| 226 | + ["zip":protected]=> |
| 227 | + int(200) |
| 228 | + ["country":protected]=> |
| 229 | + string(7) "Iceland" |
| 230 | + } |
| 231 | + } |
| 232 | + ["friends":protected]=> |
| 233 | + array(1) { |
| 234 | + [0]=> |
| 235 | + object(Person)#21 (6) { |
| 236 | + ["_id":protected]=> |
| 237 | + object(BSON\ObjectID)#18 (0) { |
| 238 | + } |
| 239 | + ["name":protected]=> |
| 240 | + string(6) "Jeremy" |
| 241 | + ["age":protected]=> |
| 242 | + int(21) |
| 243 | + ["address":protected]=> |
| 244 | + array(1) { |
| 245 | + [0]=> |
| 246 | + object(Address)#19 (2) { |
| 247 | + ["zip":protected]=> |
| 248 | + int(48169) |
| 249 | + ["country":protected]=> |
| 250 | + string(3) "USA" |
| 251 | + } |
| 252 | + } |
| 253 | + ["friends":protected]=> |
| 254 | + object(stdClass)#20 (0) { |
| 255 | + } |
| 256 | + ["secret":protected]=> |
| 257 | + string(4) "none" |
| 258 | + } |
| 259 | + } |
| 260 | + ["secret":protected]=> |
| 261 | + string(4) "none" |
| 262 | +} |
| 263 | +``` |
0 commit comments