Skip to content

Commit 7fb6ab8

Browse files
committed
PHPC-329: bson_to_zval() serialization spec compatibility
ODM behavior should override only the default type map and named class. Array and stdClass object modes still take precedence. The PHONGO_TYPEMAP_CLASS switch cases were simplified, since we can trust that the root/document or ODM class implements Unserializable (thanks to php_phongo_bson_typemap_to_state() and php_phongo_bson_visit_binary(), respectively).
1 parent 2551570 commit 7fb6ab8

File tree

3 files changed

+586
-43
lines changed

3 files changed

+586
-43
lines changed

src/bson.c

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -471,26 +471,28 @@ bool php_phongo_bson_visit_document(const bson_iter_t *iter ARG_UNUSED, const ch
471471
array_init(state.zchild);
472472

473473
if (!bson_iter_visit_all(&child, &php_bson_visitors, &state)) {
474-
if (state.odm) {
474+
/* If php_phongo_bson_visit_binary() finds an ODM class, it should
475+
* supersede a default type map and named document class. */
476+
if (state.odm && state.map.document_type == PHONGO_TYPEMAP_NONE) {
475477
state.map.document_type = PHONGO_TYPEMAP_CLASS;
476478
}
479+
477480
switch(state.map.document_type) {
478481
case PHONGO_TYPEMAP_NATIVE_ARRAY:
479482
add_assoc_zval(retval, key, state.zchild);
480483
Z_SET_REFCOUNT_P(state.zchild, 1);
481484
break;
482485

483-
case PHONGO_TYPEMAP_CLASS:
484-
if (instanceof_function(state.odm ? state.odm : state.map.document, php_phongo_unserializable_ce TSRMLS_CC)) {
485-
zval *obj = NULL;
486+
case PHONGO_TYPEMAP_CLASS: {
487+
zval *obj = NULL;
486488

487-
MAKE_STD_ZVAL(obj);
488-
object_init_ex(obj, state.odm ? state.odm : state.map.document);
489-
zend_call_method_with_1_params(&obj, NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, state.zchild);
490-
add_assoc_zval(retval, key, obj);
491-
zval_ptr_dtor(&state.zchild);
492-
break;
493-
}
489+
MAKE_STD_ZVAL(obj);
490+
object_init_ex(obj, state.odm ? state.odm : state.map.document);
491+
zend_call_method_with_1_params(&obj, NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, state.zchild);
492+
add_assoc_zval(retval, key, obj);
493+
zval_ptr_dtor(&state.zchild);
494+
break;
495+
}
494496

495497
case PHONGO_TYPEMAP_NATIVE_OBJECT:
496498
default:
@@ -522,23 +524,17 @@ bool php_phongo_bson_visit_array(const bson_iter_t *iter ARG_UNUSED, const char
522524
if (!bson_iter_visit_all(&child, &php_bson_visitors, &state)) {
523525

524526
switch(state.map.array_type) {
525-
case PHONGO_TYPEMAP_CLASS:
526-
if (instanceof_function(state.map.array, php_phongo_unserializable_ce TSRMLS_CC)) {
527-
zval *obj = NULL;
528-
529-
MAKE_STD_ZVAL(obj);
530-
object_init_ex(obj, state.map.array);
531-
zend_call_method_with_1_params(&obj, NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, state.zchild);
532-
add_assoc_zval(retval, key, obj);
533-
zval_ptr_dtor(&state.zchild);
534-
break;
535-
}
536-
/* If the object someehow doesn't implement php_phongo_unserializable_ce then use stdclass.
537-
* This is needed as we need to know how to pass the state.zchild to the class to populate it.
538-
* Not all classes have ctor that accepts first parameter array of values.
539-
*/
527+
case PHONGO_TYPEMAP_CLASS: {
528+
zval *obj = NULL;
529+
530+
MAKE_STD_ZVAL(obj);
531+
object_init_ex(obj, state.map.array);
532+
zend_call_method_with_1_params(&obj, NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, state.zchild);
533+
add_assoc_zval(retval, key, obj);
534+
zval_ptr_dtor(&state.zchild);
535+
break;
536+
}
540537

541-
/* break intentionally omitted */
542538
case PHONGO_TYPEMAP_NATIVE_OBJECT:
543539
object_and_properties_init(state.zchild, zend_standard_class_def, Z_ARRVAL_P(state.zchild));
544540
add_assoc_zval(retval, key, state.zchild);
@@ -910,9 +906,9 @@ int bson_to_zval(const unsigned char *data, int data_len, php_phongo_bson_state
910906
array_init(state->zchild);
911907
bson_iter_visit_all(&iter, &php_bson_visitors, state);
912908

913-
/* If php_phongo_bson_visit_binary() finds an ODM class, it supersedes our
914-
* document type. */
915-
if (state->odm) {
909+
/* If php_phongo_bson_visit_binary() finds an ODM class, it should supersede
910+
* a default type map and named root class. */
911+
if (state->odm && state->map.root_type == PHONGO_TYPEMAP_NONE) {
916912
state->map.root_type = PHONGO_TYPEMAP_CLASS;
917913
}
918914

@@ -921,19 +917,16 @@ int bson_to_zval(const unsigned char *data, int data_len, php_phongo_bson_state
921917
/* Nothing to do here */
922918
break;
923919

924-
case PHONGO_TYPEMAP_CLASS:
925-
/* If the class implements Unserializable, initialize the object
926-
* from our array data; otherwise, fall through to native object. */
927-
if (instanceof_function(state->odm ? state->odm : state->map.root, php_phongo_unserializable_ce TSRMLS_CC)) {
928-
zval *obj = NULL;
929-
930-
MAKE_STD_ZVAL(obj);
931-
object_init_ex(obj, state->odm ? state->odm : state->map.root);
932-
zend_call_method_with_1_params(&obj, NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, state->zchild);
933-
zval_ptr_dtor(&state->zchild);
934-
state->zchild = obj;
935-
break;
936-
}
920+
case PHONGO_TYPEMAP_CLASS: {
921+
zval *obj = NULL;
922+
923+
MAKE_STD_ZVAL(obj);
924+
object_init_ex(obj, state->odm ? state->odm : state->map.root);
925+
zend_call_method_with_1_params(&obj, NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, state->zchild);
926+
zval_ptr_dtor(&state->zchild);
927+
state->zchild = obj;
928+
break;
929+
}
937930

938931
case PHONGO_TYPEMAP_NATIVE_OBJECT:
939932
default:

tests/bson/bson-toPHP-002.phpt

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
--TEST--
2+
BSON\fromPHP(): Null type map values imply default behavior
3+
--SKIPIF--
4+
<?php require __DIR__ . "/../utils/basic-skipif.inc"?>
5+
--FILE--
6+
<?php
7+
use MongoDB\BSON as BSON;
8+
9+
require_once __DIR__ . "/../utils/basic.inc";
10+
11+
class MyDocument implements BSON\Persistable
12+
{
13+
public $data;
14+
15+
public function __construct()
16+
{
17+
$this->data = array(
18+
'list' => array(1, 2, 3),
19+
'map' => (object) array('foo' => 'bar'),
20+
);
21+
}
22+
23+
public function bsonSerialize()
24+
{
25+
return $this->data;
26+
}
27+
28+
public function bsonUnserialize(array $data)
29+
{
30+
foreach (array('list', 'map') as $key) {
31+
if (isset($data[$key])) {
32+
$this->data[$key] = $data[$key];
33+
}
34+
}
35+
}
36+
}
37+
38+
$bson = fromPHP(new MyDocument);
39+
echo "Test ", toJSON($bson), "\n";
40+
hex_dump($bson);
41+
42+
$typeMap = array(
43+
'array' => null,
44+
'document' => null,
45+
'root' => null,
46+
);
47+
48+
var_dump(toPHP($bson, $typeMap));
49+
50+
?>
51+
===DONE===
52+
<?php exit(0); ?>
53+
--EXPECTF--
54+
Test { "__pclass" : { "$type" : "80", "$binary" : "TXlEb2N1bWVudA==" }, "list" : [ 1, 2, 3 ], "map" : { "foo" : "bar" } }
55+
0 : 55 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 0a 00 [U....__pclass...]
56+
10 : 00 00 80 4d 79 44 6f 63 75 6d 65 6e 74 04 6c 69 [...MyDocument.li]
57+
20 : 73 74 00 1a 00 00 00 10 30 00 01 00 00 00 10 31 [st......0......1]
58+
30 : 00 02 00 00 00 10 32 00 03 00 00 00 00 03 6d 61 [......2.......ma]
59+
40 : 70 00 12 00 00 00 02 66 6f 6f 00 04 00 00 00 62 [p......foo.....b]
60+
50 : 61 72 00 00 00 [ar...]
61+
object(MyDocument)#%d (1) {
62+
["data"]=>
63+
array(2) {
64+
["list"]=>
65+
array(3) {
66+
[0]=>
67+
int(1)
68+
[1]=>
69+
int(2)
70+
[2]=>
71+
int(3)
72+
}
73+
["map"]=>
74+
object(stdClass)#%d (1) {
75+
["foo"]=>
76+
string(3) "bar"
77+
}
78+
}
79+
}
80+
===DONE===

0 commit comments

Comments
 (0)