Skip to content

Commit deb4fcd

Browse files
committed
PHPC-640: Handle TypeWrappers during BSON serialization
1 parent 60ec72a commit deb4fcd

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

src/bson-encode.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
#undef MONGOC_LOG_DOMAIN
4646
#define MONGOC_LOG_DOMAIN "PHONGO-BSON"
4747

48+
/* Forward declarations */
49+
static void php_phongo_bson_append(bson_t *bson, php_phongo_bson_flags_t flags, const char *key, long key_len, zval *entry TSRMLS_DC);
50+
4851
/* Determines whether the argument should be serialized as a BSON array or
4952
* document. IS_ARRAY is returned if the argument's keys are a sequence of
5053
* integers starting at zero; otherwise, IS_OBJECT is returned. */
@@ -118,6 +121,33 @@ static void php_phongo_bson_append_object(bson_t *bson, php_phongo_bson_flags_t
118121
return;
119122
}
120123

124+
if (Z_TYPE_P(object) == IS_OBJECT && instanceof_function(Z_OBJCE_P(object), php_phongo_typewrapper_ce TSRMLS_CC)) {
125+
#if PHP_VERSION_ID >= 70000
126+
zval retval;
127+
128+
zend_call_method_with_0_params(object, NULL, NULL, "toBSONType", &retval);
129+
#else
130+
zval *retval;
131+
132+
zend_call_method_with_0_params(&object, NULL, NULL, "toBSONType", &retval);
133+
#endif
134+
135+
if (Z_ISUNDEF(retval)) {
136+
/* zend_call_method() failed */
137+
return;
138+
}
139+
140+
#if PHP_VERSION_ID >= 70000
141+
php_phongo_bson_append(bson, flags, key, key_len, &retval TSRMLS_CC);
142+
#else
143+
php_phongo_bson_append(bson, flags, key, key_len, retval TSRMLS_CC);
144+
#endif
145+
146+
zval_ptr_dtor(&retval);
147+
148+
return;
149+
}
150+
121151
if (Z_TYPE_P(object) == IS_OBJECT && instanceof_function(Z_OBJCE_P(object), php_phongo_type_ce TSRMLS_CC)) {
122152
if (instanceof_function(Z_OBJCE_P(object), php_phongo_serializable_ce TSRMLS_CC)) {
123153
#if PHP_VERSION_ID >= 70000

tests/bson/bson-fromPHP-007.phpt

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
--TEST--
2+
MongoDB\BSON\fromPHP(): Type wrappers
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/../utils/tools.php';
7+
8+
class MyWrapper implements MongoDB\BSON\TypeWrapper
9+
{
10+
private $bsonType;
11+
12+
public function __construct($bsonType)
13+
{
14+
$this->bsonType = $bsonType;
15+
}
16+
17+
public static function createFromBSONType(MongoDB\BSON\Type $bsonType)
18+
{
19+
return new self($bsonType);
20+
}
21+
22+
public function toBSONType()
23+
{
24+
return $this->bsonType;
25+
}
26+
}
27+
28+
class MyDocument implements MongoDB\BSON\Serializable
29+
{
30+
private $data;
31+
32+
public function __construct($data = null)
33+
{
34+
$this->data = $data;
35+
}
36+
37+
public function bsonSerialize()
38+
{
39+
return $this->data;
40+
}
41+
}
42+
43+
$document = [
44+
// Basic PHP types
45+
'null' => new MyWrapper(null),
46+
'boolean' => new MyWrapper(true),
47+
'integer' => new MyWrapper(123),
48+
'double' => new MyWrapper(4.125),
49+
'string' => new MyWrapper('foo'),
50+
'array' => new MyWrapper([1, 2, 3]),
51+
'document' => new MyWrapper(['foo' => 1]),
52+
// MongoDB\BSON\Type classes
53+
'Binary' => new MyWrapper(new MongoDB\BSON\Binary('foo', MongoDB\BSON\Binary::TYPE_GENERIC)),
54+
'Decimal128' => new MyWrapper(new MongoDB\BSON\Decimal128('1234.5678')),
55+
'Javascript' => new MyWrapper(new MongoDB\BSON\Javascript('function foo() { return 1; }')),
56+
'MaxKey' => new MyWrapper(new MongoDB\BSON\MaxKey),
57+
'MinKey' => new MyWrapper(new MongoDB\BSON\MinKey),
58+
'ObjectID' => new MyWrapper(new MongoDB\BSON\ObjectId('58d17cef784c6b405fec16a1')),
59+
'Regex' => new MyWrapper(new MongoDB\BSON\Regex('pattern', 'i')),
60+
'Timestamp' => new MyWrapper(new MongoDB\BSON\Timestamp(1234, 5678)),
61+
'UTCDateTime' => new MyWrapper(new MongoDB\BSON\UTCDateTime(1490124067000)),
62+
// MongoDB\BSON\Serializable interface
63+
'Serializable' => new MyWrapper(new MyDocument(['bar' => 1])),
64+
];
65+
66+
var_dump(toPHP(fromPHP($document)));
67+
68+
?>
69+
===DONE===
70+
<?php exit(0); ?>
71+
--EXPECTF--
72+
object(stdClass)#%d (%d) {
73+
["null"]=>
74+
NULL
75+
["boolean"]=>
76+
bool(true)
77+
["integer"]=>
78+
int(123)
79+
["double"]=>
80+
float(4.125)
81+
["string"]=>
82+
string(3) "foo"
83+
["array"]=>
84+
array(3) {
85+
[0]=>
86+
int(1)
87+
[1]=>
88+
int(2)
89+
[2]=>
90+
int(3)
91+
}
92+
["document"]=>
93+
object(stdClass)#%d (%d) {
94+
["foo"]=>
95+
int(1)
96+
}
97+
["Binary"]=>
98+
object(MongoDB\BSON\Binary)#%d (%d) {
99+
["data"]=>
100+
string(3) "foo"
101+
["type"]=>
102+
int(0)
103+
}
104+
["Decimal128"]=>
105+
object(MongoDB\BSON\Decimal128)#%d (%d) {
106+
["dec"]=>
107+
string(9) "1234.5678"
108+
}
109+
["Javascript"]=>
110+
object(MongoDB\BSON\Javascript)#%d (%d) {
111+
["code"]=>
112+
string(28) "function foo() { return 1; }"
113+
["scope"]=>
114+
NULL
115+
}
116+
["MaxKey"]=>
117+
object(MongoDB\BSON\MaxKey)#%d (%d) {
118+
}
119+
["MinKey"]=>
120+
object(MongoDB\BSON\MinKey)#%d (%d) {
121+
}
122+
["ObjectID"]=>
123+
object(MongoDB\BSON\ObjectID)#%d (%d) {
124+
["oid"]=>
125+
string(24) "58d17cef784c6b405fec16a1"
126+
}
127+
["Regex"]=>
128+
object(MongoDB\BSON\Regex)#%d (%d) {
129+
["pattern"]=>
130+
string(7) "pattern"
131+
["flags"]=>
132+
string(1) "i"
133+
}
134+
["Timestamp"]=>
135+
object(MongoDB\BSON\Timestamp)#%d (%d) {
136+
["increment"]=>
137+
string(4) "1234"
138+
["timestamp"]=>
139+
string(4) "5678"
140+
}
141+
["UTCDateTime"]=>
142+
object(MongoDB\BSON\UTCDateTime)#%d (%d) {
143+
["milliseconds"]=>
144+
string(13) "1490124067000"
145+
}
146+
["Serializable"]=>
147+
object(stdClass)#%d (%d) {
148+
["bar"]=>
149+
int(1)
150+
}
151+
}
152+
===DONE===
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
MongoDB\BSON\fromPHP(): Serializable with circular references
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/../utils/tools.php';
7+
8+
class MyRecursiveWrapper implements MongoDB\BSON\TypeWrapper
9+
{
10+
private $bsonType;
11+
12+
public function __construct($bsonType)
13+
{
14+
$this->bsonType = $bsonType;
15+
}
16+
17+
public static function createFromBSONType(MongoDB\BSON\Type $bsonType)
18+
{
19+
return new self($bsonType);
20+
}
21+
22+
public function toBSONType()
23+
{
24+
return $this;
25+
}
26+
}
27+
28+
class MyIndirectlyRecursiveWrapper extends MyRecursiveWrapper
29+
{
30+
public function toBSONType()
31+
{
32+
return ['x' => $this];
33+
}
34+
}
35+
36+
echo "\nTesting TypeWrapper with direct circular reference\n";
37+
38+
echo throws(function() {
39+
fromPHP(['x' => new MyRecursiveWrapper(123)]);
40+
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";
41+
42+
echo "\nTesting TypeWrapper with indirect circular reference\n";
43+
44+
echo throws(function() {
45+
fromPHP(['x' => new MyIndirectlyRecursiveWrapper(123)]);
46+
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";
47+
48+
?>
49+
===DONE===
50+
<?php exit(0); ?>
51+
--EXPECT--
52+
Testing TypeWrapper with direct circular reference
53+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
54+
Detected recursion for fieldname "x"
55+
56+
Testing TypeWrapper with indirect circular reference
57+
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
58+
Detected recursion for fieldname "x"
59+
===DONE===

0 commit comments

Comments
 (0)