6262
6363#define PHONGO_ODM_FIELD_NAME "__pclass"
6464
65+ #define PHONGO_IS_CLASS_INSTANTIATABLE (ce ) \
66+ (!(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)))
67+ 6568PHP_MINIT_FUNCTION (bson )
6669{
6770 (void )type ; /* We don't care if we are loaded via dl() or extension= */
@@ -211,8 +214,12 @@ bool php_phongo_bson_visit_binary(const bson_iter_t *iter ARG_UNUSED, const char
211214 zval * zchild = NULL ;
212215 TSRMLS_FETCH ();
213216
214- if (v_subtype == 0x80 && strcmp (key , PHONGO_ODM_FIELD_NAME ) == 0 ) {
215- ((php_phongo_bson_state * )data )-> odm = zend_fetch_class ((char * )v_binary , v_binary_len , ZEND_FETCH_CLASS_AUTO |ZEND_FETCH_CLASS_SILENT TSRMLS_CC );
217+ if (v_subtype == 0x80 && strcmp (key , PHONGO_ODM_FIELD_NAME ) == 0 ) {
218+ zend_class_entry * found_ce = zend_fetch_class ((char * )v_binary , v_binary_len , ZEND_FETCH_CLASS_AUTO |ZEND_FETCH_CLASS_SILENT TSRMLS_CC );
219+ 220+ if (found_ce && PHONGO_IS_CLASS_INSTANTIATABLE (found_ce ) && instanceof_function (found_ce , php_phongo_persistable_ce TSRMLS_CC )) {
221+ ((php_phongo_bson_state * )data )-> odm = found_ce ;
222+ }
216223 }
217224
218225 MAKE_STD_ZVAL (zchild );
@@ -463,26 +470,28 @@ bool php_phongo_bson_visit_document(const bson_iter_t *iter ARG_UNUSED, const ch
463470 array_init (state .zchild );
464471
465472 if (!bson_iter_visit_all (& child , & php_bson_visitors , & state )) {
466- if (state .odm ) {
473+ /* If php_phongo_bson_visit_binary() finds an ODM class, it should
474+ * supersede a default type map and named document class. */
475+ if (state .odm && state .map .document_type == PHONGO_TYPEMAP_NONE ) {
467476 state .map .document_type = PHONGO_TYPEMAP_CLASS ;
468477 }
478+ 469479 switch (state .map .document_type ) {
470480 case PHONGO_TYPEMAP_NATIVE_ARRAY :
471481 add_assoc_zval (retval , key , state .zchild );
472482 Z_SET_REFCOUNT_P (state .zchild , 1 );
473483 break ;
474484
475- case PHONGO_TYPEMAP_CLASS :
476- if (instanceof_function (state .odm ? state .odm : state .map .document , php_phongo_unserializable_ce TSRMLS_CC )) {
477- zval * obj = NULL ;
485+ case PHONGO_TYPEMAP_CLASS : {
486+ zval * obj = NULL ;
478487
479- MAKE_STD_ZVAL (obj );
480- object_init_ex (obj , state .odm ? state .odm : state .map .document );
481- zend_call_method_with_1_params (& obj , NULL , NULL , BSON_UNSERIALIZE_FUNC_NAME , NULL , state .zchild );
482- add_assoc_zval (retval , key , obj );
483- zval_ptr_dtor (& state .zchild );
484- break ;
485- }
488+ MAKE_STD_ZVAL (obj );
489+ object_init_ex (obj , state .odm ? state .odm : state .map .document );
490+ zend_call_method_with_1_params (& obj , NULL , NULL , BSON_UNSERIALIZE_FUNC_NAME , NULL , state .zchild );
491+ add_assoc_zval (retval , key , obj );
492+ zval_ptr_dtor (& state .zchild );
493+ break ;
494+ }
486495
487496 case PHONGO_TYPEMAP_NATIVE_OBJECT :
488497 default :
@@ -514,23 +523,17 @@ bool php_phongo_bson_visit_array(const bson_iter_t *iter ARG_UNUSED, const char
514523 if (!bson_iter_visit_all (& child , & php_bson_visitors , & state )) {
515524
516525 switch (state .map .array_type ) {
517- case PHONGO_TYPEMAP_CLASS :
518- if (instanceof_function (state .map .array , php_phongo_unserializable_ce TSRMLS_CC )) {
519- zval * obj = NULL ;
520- 521- MAKE_STD_ZVAL (obj );
522- object_init_ex (obj , state .map .array );
523- zend_call_method_with_1_params (& obj , NULL , NULL , BSON_UNSERIALIZE_FUNC_NAME , NULL , state .zchild );
524- add_assoc_zval (retval , key , obj );
525- zval_ptr_dtor (& state .zchild );
526- break ;
527- }
528- /* If the object someehow doesn't implement php_phongo_unserializable_ce then use stdclass.
529- * This is needed as we need to know how to pass the state.zchild to the class to populate it.
530- * Not all classes have ctor that accepts first parameter array of values.
531- */
526+ case PHONGO_TYPEMAP_CLASS : {
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+ }
532536
533- /* break intentionally omitted */
534537 case PHONGO_TYPEMAP_NATIVE_OBJECT :
535538 object_and_properties_init (state .zchild , zend_standard_class_def , Z_ARRVAL_P (state .zchild ));
536539 add_assoc_zval (retval , key , state .zchild );
@@ -617,8 +620,8 @@ void object_to_bson(zval *object, php_phongo_bson_flags_t flags, const char *key
617620 return ;
618621 }
619622
620- if (Z_TYPE_P (obj_data ) != IS_ARRAY ) {
621- phongo_throw_exception (PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC , "Expected %s() to return an array, %s given" , BSON_SERIALIZE_FUNC_NAME , zend_get_type_by_const (Z_TYPE_P (obj_data )));
623+ if (Z_TYPE_P (obj_data ) != IS_ARRAY && !( Z_TYPE_P ( obj_data ) == IS_OBJECT && instanceof_function ( Z_OBJCE_P ( obj_data ), zend_standard_class_def TSRMLS_CC )) ) {
624+ phongo_throw_exception (PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC , "Expected %s::%s () to return an array or stdClass , %s given" , Z_OBJCE_P ( object ) -> name , BSON_SERIALIZE_FUNC_NAME , ( Z_TYPE_P ( obj_data ) == IS_OBJECT ? Z_OBJCE_P ( obj_data ) -> name : zend_get_type_by_const (Z_TYPE_P (obj_data ) )));
622625 zval_ptr_dtor (& obj_data );
623626
624627 return ;
@@ -630,14 +633,22 @@ void object_to_bson(zval *object, php_phongo_bson_flags_t flags, const char *key
630633 tmp_ht -> nApplyCount ++ ;
631634 }
632635
633- bson_append_document_begin (bson , key , key_len , & child );
634- if (instanceof_function (Z_OBJCE_P (object ), php_phongo_persistable_ce TSRMLS_CC )) {
635- if (flags & PHONGO_BSON_ADD_CHILD_ODS ) {
636- bson_append_binary (& child , PHONGO_ODM_FIELD_NAME , -1 , 0x80 , (const uint8_t * )Z_OBJCE_P (object )-> name , strlen (Z_OBJCE_P (object )-> name ));
636+ /* Persistable objects must always be serialized as BSON documents;
637+ * otherwise, infer based on bsonSerialize()'s return value. */
638+ if (instanceof_function (Z_OBJCE_P (object ), php_phongo_persistable_ce TSRMLS_CC ) || php_phongo_is_array_or_document (& obj_data TSRMLS_CC ) == IS_OBJECT ) {
639+ bson_append_document_begin (bson , key , key_len , & child );
640+ if (instanceof_function (Z_OBJCE_P (object ), php_phongo_persistable_ce TSRMLS_CC )) {
641+ if (flags & PHONGO_BSON_ADD_CHILD_ODS ) {
642+ bson_append_binary (& child , PHONGO_ODM_FIELD_NAME , -1 , 0x80 , (const uint8_t * )Z_OBJCE_P (object )-> name , strlen (Z_OBJCE_P (object )-> name ));
643+ }
637644 }
645+ zval_to_bson (obj_data , flags , & child , NULL TSRMLS_CC );
646+ bson_append_document_end (bson , & child );
647+ } else {
648+ bson_append_array_begin (bson , key , key_len , & child );
649+ zval_to_bson (obj_data , flags , & child , NULL TSRMLS_CC );
650+ bson_append_array_end (bson , & child );
638651 }
639- zval_to_bson (obj_data , flags , & child , NULL TSRMLS_CC );
640- bson_append_document_end (bson , & child );
641652
642653 if (tmp_ht ) {
643654 tmp_ht -> nApplyCount -- ;
@@ -779,8 +790,8 @@ PHONGO_API void zval_to_bson(zval *data, php_phongo_bson_flags_t flags, bson_t *
779790 break ;
780791 }
781792
782- if (Z_TYPE_P (obj_data ) != IS_ARRAY ) {
783- phongo_throw_exception (PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC , "Expected %s() to return an array, %s given" , BSON_SERIALIZE_FUNC_NAME , zend_get_type_by_const (Z_TYPE_P (obj_data )));
793+ if (Z_TYPE_P (obj_data ) != IS_ARRAY && !( Z_TYPE_P ( obj_data ) == IS_OBJECT && instanceof_function ( Z_OBJCE_P ( obj_data ), zend_standard_class_def TSRMLS_CC )) ) {
794+ phongo_throw_exception (PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC , "Expected %s::%s () to return an array or stdClass , %s given" , Z_OBJCE_P ( data ) -> name , BSON_SERIALIZE_FUNC_NAME , ( Z_TYPE_P ( obj_data ) == IS_OBJECT ? Z_OBJCE_P ( obj_data ) -> name : zend_get_type_by_const (Z_TYPE_P (obj_data ) )));
784795
785796 break ;
786797 }
@@ -902,9 +913,9 @@ int bson_to_zval(const unsigned char *data, int data_len, php_phongo_bson_state
902913 array_init (state -> zchild );
903914 bson_iter_visit_all (& iter , & php_bson_visitors , state );
904915
905- /* If php_phongo_bson_visit_binary() finds an ODM class, it supersedes our
906- * document type. */
907- if (state -> odm ) {
916+ /* If php_phongo_bson_visit_binary() finds an ODM class, it should supersede
917+ * a default type map and named root class . */
918+ if (state -> odm && state -> map . root_type == PHONGO_TYPEMAP_NONE ) {
908919 state -> map .root_type = PHONGO_TYPEMAP_CLASS ;
909920 }
910921
@@ -913,19 +924,16 @@ int bson_to_zval(const unsigned char *data, int data_len, php_phongo_bson_state
913924 /* Nothing to do here */
914925 break ;
915926
916- case PHONGO_TYPEMAP_CLASS :
917- /* If the class implements Unserializable, initialize the object
918- * from our array data; otherwise, fall through to native object. */
919- if (instanceof_function (state -> odm ? state -> odm : state -> map .root , php_phongo_unserializable_ce TSRMLS_CC )) {
920- zval * obj = NULL ;
921- 922- MAKE_STD_ZVAL (obj );
923- object_init_ex (obj , state -> odm ? state -> odm : state -> map .root );
924- zend_call_method_with_1_params (& obj , NULL , NULL , BSON_UNSERIALIZE_FUNC_NAME , NULL , state -> zchild );
925- zval_ptr_dtor (& state -> zchild );
926- state -> zchild = obj ;
927- break ;
928- }
927+ case PHONGO_TYPEMAP_CLASS : {
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+ }
929937
930938 case PHONGO_TYPEMAP_NATIVE_OBJECT :
931939 default :
@@ -964,71 +972,63 @@ PHP_FUNCTION(fromPHP)
964972}
965973/* }}} */
966974
975+ static void apply_classname_to_state (const char * classname , int classname_len , php_phongo_bson_typemap_types * type , zend_class_entry * * type_ce TSRMLS_DC )
976+ {
977+ if (!strcasecmp (classname , "array" )) {
978+ * type = PHONGO_TYPEMAP_NATIVE_ARRAY ;
979+ * type_ce = NULL ;
980+ } else if (!strcasecmp (classname , "stdclass" ) || !strcasecmp (classname , "object" )) {
981+ * type = PHONGO_TYPEMAP_NATIVE_OBJECT ;
982+ * type_ce = NULL ;
983+ } else {
984+ zend_class_entry * found_ce = zend_fetch_class (classname , classname_len , ZEND_FETCH_CLASS_AUTO |ZEND_FETCH_CLASS_SILENT TSRMLS_CC );
985+ 986+ if (!found_ce ) {
987+ phongo_throw_exception (PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC , "Class %s does not exist" , classname );
988+ } else if (!PHONGO_IS_CLASS_INSTANTIATABLE (found_ce )) {
989+ phongo_throw_exception (PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC , "Class %s is not instantiatable" , classname );
990+ } else if (!instanceof_function (found_ce , php_phongo_unserializable_ce TSRMLS_CC )) {
991+ phongo_throw_exception (PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC , "Class %s does not implement %s\\Unserializable" , classname , BSON_NAMESPACE );
992+ } else {
993+ * type = PHONGO_TYPEMAP_CLASS ;
994+ * type_ce = found_ce ;
995+ }
996+ }
997+ }
998+ 967999void php_phongo_bson_typemap_to_state (zval * typemap , php_phongo_bson_typemap * map TSRMLS_DC )
9681000{
9691001 if (typemap ) {
970- char * classname ;
971- int classname_len ;
972- zend_bool classname_free = 0 ;
1002+ char * classname ;
1003+ int classname_len ;
1004+ zend_bool classname_free = 0 ;
9731005
9741006 classname = php_array_fetchl_string (typemap , "array" , sizeof ("array" )- 1 , & classname_len , & classname_free );
9751007 if (classname_len ) {
976- if (!strcasecmp (classname , "array" )) {
977- map -> array_type = PHONGO_TYPEMAP_NATIVE_ARRAY ;
978- } else if (!strcasecmp (classname , "stdclass" ) || !strcasecmp (classname , "object" )) {
979- map -> array_type = PHONGO_TYPEMAP_NATIVE_OBJECT ;
980- } else {
981- zend_class_entry * array_ce = zend_fetch_class (classname , classname_len , ZEND_FETCH_CLASS_AUTO TSRMLS_CC );
982- map -> array_type = PHONGO_TYPEMAP_CLASS ;
983- 984- if (instanceof_function (array_ce , php_phongo_unserializable_ce TSRMLS_CC )) {
985- map -> array = array_ce ;
986- }
987- }
988- if (classname_free ) {
989- efree (classname );
990- }
1008+ apply_classname_to_state (classname , classname_len , & map -> array_type , & map -> array TSRMLS_CC );
1009+ }
1010+ if (classname_free ) {
1011+ str_efree (classname );
9911012 }
9921013
9931014 classname = php_array_fetchl_string (typemap , "document" , sizeof ("document" )- 1 , & classname_len , & classname_free );
9941015 if (classname_len ) {
995- if (!strcasecmp (classname , "array" )) {
996- map -> document_type = PHONGO_TYPEMAP_NATIVE_ARRAY ;
997- } else if (!strcasecmp (classname , "stdclass" ) || !strcasecmp (classname , "object" )) {
998- map -> document_type = PHONGO_TYPEMAP_NATIVE_OBJECT ;
999- } else {
1000- zend_class_entry * document_ce = zend_fetch_class (classname , classname_len , ZEND_FETCH_CLASS_AUTO TSRMLS_CC );
1001- map -> document_type = PHONGO_TYPEMAP_CLASS ;
1002- 1003- if (instanceof_function (document_ce , php_phongo_unserializable_ce TSRMLS_CC )) {
1004- map -> document = document_ce ;
1005- }
1006- }
1007- if (classname_free ) {
1008- efree (classname );
1009- }
1016+ apply_classname_to_state (classname , classname_len , & map -> document_type , & map -> document TSRMLS_CC );
1017+ }
1018+ if (classname_free ) {
1019+ str_efree (classname );
10101020 }
10111021
10121022 classname = php_array_fetchl_string (typemap , "root" , sizeof ("root" )- 1 , & classname_len , & classname_free );
10131023 if (classname_len ) {
1014- if (!strcasecmp (classname , "array" )) {
1015- map -> root_type = PHONGO_TYPEMAP_NATIVE_ARRAY ;
1016- } else if (!strcasecmp (classname , "stdclass" ) || !strcasecmp (classname , "object" )) {
1017- map -> root_type = PHONGO_TYPEMAP_NATIVE_OBJECT ;
1018- } else {
1019- zend_class_entry * root_ce = zend_fetch_class (classname , classname_len , ZEND_FETCH_CLASS_AUTO TSRMLS_CC );
1020- map -> root_type = PHONGO_TYPEMAP_CLASS ;
1021- 1022- if (instanceof_function (root_ce , php_phongo_unserializable_ce TSRMLS_CC )) {
1023- map -> root = root_ce ;
1024- }
1025- }
1026- if (classname_free ) {
1027- efree (classname );
1028- }
1024+ apply_classname_to_state (classname , classname_len , & map -> root_type , & map -> root TSRMLS_CC );
1025+ }
1026+ if (classname_free ) {
1027+ str_efree (classname );
10291028 }
10301029 }
10311030}
1031+ 10321032/* {{{ proto array|object BSON\toPHP(string $bson [, array $typemap = array()])
10331033 Returns the PHP representation of a BSON value, optionally converting it into a custom class */
10341034PHP_FUNCTION (toPHP )
0 commit comments