Skip to content

Commit d11507b

Browse files
committed
Merged pull request #571
2 parents 25fc3d1 + e002bcd commit d11507b

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

source/tutorial.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Tutorials
88
/tutorial/crud
99
/tutorial/collation
1010
/tutorial/commands
11+
/tutorial/custom-types
1112
/tutorial/decimal128
1213
/tutorial/gridfs
1314
/tutorial/indexes

source/tutorial/custom-types.txt

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
=================
2+
Custom Data-Types
3+
=================
4+
5+
.. default-domain:: mongodb
6+
7+
The MongoDB PHP extension and library support custom classes while
8+
serializing and deserializing. An example of where this might be useful is
9+
if you want to store date/time information retaining the time zone
10+
information that PHP's :php:`DateTimeImmutable <class.datetimeimmutable>`
11+
class stores with a point in time.
12+
13+
The driver serializes PHP variables, including objects, into BSON when it
14+
communicates to the server, and deserializes BSON back into PHP variables when
15+
it receives data from the server.
16+
17+
It is possible to influence the behaviour by implementing the
18+
:php:`MongoDB\\BSON\\Persistable <class.mongodb-bson-persistable>` interface.
19+
If a class implements this interface, then upon serialization the
20+
:php:`bsonSerialize <mongodb-bson-serializable.bsonserialize>` method is
21+
called. This method is responsible for returning an array or stdClass object
22+
to convert to BSON and store in the database. That data will later be used to
23+
reconstruct the object upon reading from the database.
24+
25+
As an example we present the ``LocalDateTime`` class. This class wraps around
26+
the :php:`MongoDB\\BSON\UTCDateTime <class.mongodb-bson-utcdatetime>` data
27+
type and a time zone.
28+
29+
.. code-block:: php
30+
31+
<?php
32+
/* Custom document class that stores a UTCDateTime and time zone and also
33+
* implements the UTCDateTime interface for portability. */
34+
class LocalDateTime implements \MongoDB\BSON\Persistable, \MongoDB\BSON\UTCDateTimeInterface
35+
{
36+
private $utc;
37+
private $tz;
38+
public function __construct($milliseconds = null, \DateTimeZone $timezone = null)
39+
{
40+
$this->utc = new \MongoDB\BSON\UTCDateTime($milliseconds);
41+
if ($timezone === null) {
42+
$timezone = new \DateTimeZone(date_default_timezone_get());
43+
}
44+
$this->tz = $timezone;
45+
}
46+
?>
47+
48+
As it implements the :php:`MongoDB\\BSON\\Persistable
49+
<class.mongodb-bson-persistable>` interface, the
50+
class is required to implement the :php:`bsonSerialize
51+
<mongodb-bson-serializable.bsonserialize>` and :php:`bsonUnserialize
52+
<mongodb-bson-unserializable.bsonunserialize>` methods. In the
53+
:php:`bsonSerialize <mongodb-bson-serializable.bsonserialize>` method, we
54+
return an array with the two values that we need to persist: the point in time
55+
in milliseconds since the Epoch, represented by a
56+
:php:`MongoDB\\BSON\\UTCDateTime <class.mongodb-bson-utcdatetime>` object, and
57+
a string containing the Olson time zone identifier:
58+
59+
.. code-block:: php
60+
61+
<?php
62+
public function bsonSerialize()
63+
{
64+
return [
65+
'utc' => $this->utc,
66+
'tz' => $this->tz->getName(),
67+
];
68+
}
69+
?>
70+
71+
The driver will additionally add a ``__pclass`` field to the document, and
72+
store that in the database, too. This field contains the PHP class name so that
73+
upon deserialization the driver knows which class to use for recreating the
74+
stored object.
75+
76+
When the document is read from the database, the driver detects whether a
77+
``__pclass`` field is present and then executes
78+
:php:`MongoDB\\BSON\\Persistable::bsonUnserialize
79+
<mongodb-bson-unserializable.bsonunserialize>` method which is
80+
responsible for restoring the object's original state.
81+
82+
In the code below, we make sure that the data in the ``utc`` and ``tz`` fields
83+
are of the right time, and then assign their values to the two private
84+
properties.
85+
86+
.. code-block:: php
87+
88+
<?php
89+
public function bsonUnserialize(array $data)
90+
{
91+
if ( ! isset($data['utc']) || ! $data['utc'] instanceof \MongoDB\BSON\UTCDateTime) {
92+
throw new Exception('Expected "utc" field to be a UTCDateTime');
93+
}
94+
95+
if ( ! isset($data['tz']) || ! is_string($data['tz'])) {
96+
throw new Exception('Expected "tz" field to be a string');
97+
}
98+
99+
$this->utc = $data['utc'];
100+
$this->tz = new \DateTimeZone($data['tz']);
101+
}
102+
?>
103+
104+
You may have noticed that the class also implements the
105+
:php:`MongoDB\\BSON\\UTCDateTimeInterface
106+
<class.mongodb-bson-utcdatetimeinterface>` interface. This interface defines
107+
the two non-constructor methods of the :php:`MongoDB\\BSON\\UTCDateTime
108+
<class.mongodb-bson-utcdatetime>` class.
109+
110+
It is recommended that wrappers around existing BSON classes implement their
111+
respective interface (i.e. :php:`MongoDB\\BSON\\UTCDateTimeInterface
112+
<class.mongodb-bson-utcdatetimeinterface>`) so that the wrapper objects can be
113+
used in the same context as their original unwrapped version. It is also
114+
recommended that you always type-hint against the interface (i.e.
115+
:php:`MongoDB\\BSON\\UTCDateTimeInterface
116+
<class.mongodb-bson-utcdatetimeinterface>`) and never against the concrete
117+
class (i.e. :php:`MongoDB\\BSON\\UTCDateTime
118+
<class.mongodb-bson-utcdatetime>`), as this would prevent wrapped objects from
119+
being accepted into methods.
120+
121+
In our new ``toDateTime`` method we return a :php:`DateTime <class.datetime>`
122+
object with the local time zone set, instead of the UTC time zone that
123+
:php:`MongoDB\\BSON\\UTCDateTime <class.mongodb-bson-utcdatetime>` normally uses
124+
in its return value.
125+
126+
.. code-block:: php
127+
128+
<?php
129+
public function toDateTime()
130+
{
131+
return $this->utc->toDateTime()->setTimezone($this->tz);
132+
}
133+
134+
public function __toString()
135+
{
136+
return (string) $this->utc;
137+
}
138+
}
139+
?>
140+
141+
With the class defined, we can now use it in our documents. The snippet below
142+
demonstrates the round tripping from the ``LocalDateTime`` object to BSON, and
143+
back to ``LocalDateTime``.
144+
145+
.. code-block:: php
146+
147+
<?php
148+
$bson = MongoDB\BSON\fromPHP(['date' => new LocalDateTime]);
149+
$document = MongoDB\BSON\toPHP($bson);
150+
151+
var_dump($document);
152+
var_dump($document->date->toDateTime());
153+
?>
154+
155+
Which outputs::
156+
157+
object(stdClass)#1 (1) {
158+
["date"]=>
159+
object(LocalDateTime)#2 (2) {
160+
["utc":"LocalDateTime":private]=>
161+
object(MongoDB\BSON\UTCDateTime)#3 (1) {
162+
["milliseconds"]=>
163+
string(13) "1533042443716"
164+
}
165+
["tz":"LocalDateTime":private]=>
166+
object(DateTimeZone)#4 (2) {
167+
["timezone_type"]=>
168+
int(3)
169+
["timezone"]=>
170+
string(13) "Europe/London"
171+
}
172+
}
173+
}
174+
object(DateTime)#5 (3) {
175+
["date"]=>
176+
string(26) "2018-07-31 14:07:23.716000"
177+
["timezone_type"]=>
178+
int(3)
179+
["timezone"]=>
180+
string(13) "Europe/London"
181+
}
182+
183+
Storing the Olson time zone identifier in a separate field also works well
184+
with MongoDB's :manual:`Aggregation Framework </aggregation>`, which allows
185+
date manipulation, :manual:`formatting
186+
</reference/operator/aggregation/dateToString>`, and querying depending on a
187+
specific time zone.

0 commit comments

Comments
 (0)