Skip to content

Commit 62560b0

Browse files
committed
PHPC-147: Document the ODS
1 parent 557ed02 commit 62560b0

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

docs/ods.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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+
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pages:
66
- [crud.md, "CRUD", "Basic"]
77
- [batch.md, "CRUD", "Batches"]
88
- [commands.md, Commands]
9+
- [ods.md, Object Document Serialization]
910
extra_javascript:
1011
- pretty.js
1112
- jira.js

0 commit comments

Comments
 (0)