From a902f1039d129bc178acdc3f90adaaa70dad2e24 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 27 Jul 2022 12:31:29 -0400 Subject: [PATCH 1/5] PHPC-2083: Allow enums to be instantiated during BSON decoding Enums serialize like PHP objects with a "name" property (BackedEnum instance will also have a "value") and thus become BSON documents. In order for a document to unserialize back to an enum, it must implement either Unserializable or Persistable. The bsonUnserialize() method serves no purpose for initialization, but it will still be invoked. --- src/phongo_bson.c | 134 +++++++++++++++++++++++++++- tests/bson/bson-enum-001.phpt | 85 ++++++++++++++++++ tests/bson/bson-enum-002.phpt | 104 +++++++++++++++++++++ tests/bson/bson-enum-003.phpt | 108 ++++++++++++++++++++++ tests/bson/bson-enum_error-001.phpt | 80 +++++++++++++++++ tests/bson/bson-enum_error-002.phpt | 67 ++++++++++++++ 6 files changed, 574 insertions(+), 4 deletions(-) create mode 100644 tests/bson/bson-enum-001.phpt create mode 100644 tests/bson/bson-enum-002.phpt create mode 100644 tests/bson/bson-enum-003.phpt create mode 100644 tests/bson/bson-enum_error-001.phpt create mode 100644 tests/bson/bson-enum_error-002.phpt diff --git a/src/phongo_bson.c b/src/phongo_bson.c index aa704a470..b7f534bb4 100644 --- a/src/phongo_bson.c +++ b/src/phongo_bson.c @@ -768,6 +768,55 @@ static void php_phongo_handle_field_path_entry_for_compound_type(php_phongo_bson } } +#if PHP_VERSION_ID >= 80100 +/* Resolves an enum class and case name to a zval. On error, an exception will + * have been thrown and NULL will be returned. + * + * This function is modeled after php_var_unserialize_internal in php-src. */ +static zval* resolve_enum_case(zend_class_entry* ce, const char* case_name) +{ + zval* return_value = NULL; + zend_string* c_str; + zend_class_constant* c; + + if (!(ce->ce_flags & ZEND_ACC_ENUM)) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Class '%s' is not an enum", ZSTR_VAL(ce->name)); + goto cleanup; + } + + c_str = zend_string_init(case_name, strlen(case_name), 0); + c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), c_str); + + if (!c) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Undefined constant %s::%s", ZSTR_VAL(ce->name), case_name); + goto cleanup; + } + + if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "%s::%s is not an enum case", ZSTR_VAL(ce->name), case_name); + goto cleanup; + } + + if (Z_TYPE(c->value) == IS_CONSTANT_AST && zval_update_constant_ex(&c->value, ce) == FAILURE) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Failed to evaluate constant expression AST for %s::%s", ZSTR_VAL(ce->name), case_name); + goto cleanup; + } + + if (Z_TYPE(c->value) != IS_OBJECT) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Expected %s::%s to be an object, but it is: %s", ZSTR_VAL(ce->name), case_name, PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(&c->value)); + } + + return_value = &c->value; + +cleanup: + if (c_str) { + zend_string_release_ex(c_str, 0); + } + + return return_value; +} +#endif /* PHP_VERSION_ID >= 80100 */ + static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, const char* key, const bson_t* v_document, void* data) /* {{{ */ { zval* retval = PHONGO_BSON_STATE_ZCHILD(data); @@ -805,9 +854,51 @@ static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, c break; case PHONGO_TYPEMAP_CLASS: { - zval obj; + zval obj; + zend_class_entry* obj_ce = state.odm ? state.odm : state.map.document; + +#if PHP_VERSION_ID >= 80100 + /* Enums require special handling for instantiation */ + if (obj_ce->ce_flags & ZEND_ACC_ENUM) { + int plen; + zend_bool pfree; + char* case_name; + zval* enum_case; + + case_name = php_array_fetchc_string(&state.zchild, "name", &plen, &pfree); + + if (!case_name) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Missing 'name' field to infer enum case for %s", ZSTR_VAL(obj_ce->name)); + + /* Clean up and return true to stop iteration for + * our parent context. */ + zval_ptr_dtor(&state.zchild); + php_phongo_bson_state_dtor(&state); + return true; + } + + enum_case = resolve_enum_case(obj_ce, case_name); + + if (pfree) { + efree(case_name); + } + + if (!enum_case) { + /* Exception already thrown. Clean up and return + * true to stop iteration for our parent context. */ + zval_ptr_dtor(&state.zchild); + php_phongo_bson_state_dtor(&state); + return true; + } + + ZVAL_COPY_VALUE(&obj, enum_case); + } else { + object_init_ex(&obj, obj_ce); + } +#else /* PHP_VERSION_ID < 80100 */ + object_init_ex(&obj, obj_ce); +#endif /* PHP_VERSION_ID */ - object_init_ex(&obj, state.odm ? state.odm : state.map.document); zend_call_method_with_1_params(PHONGO_COMPAT_OBJ_P(&obj), NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, &state.zchild); if (((php_phongo_bson_state*) data)->is_visiting_array) { add_next_index_zval(retval, &obj); @@ -1035,9 +1126,44 @@ bool php_phongo_bson_to_zval_ex(const unsigned char* data, int data_len, php_pho break; case PHONGO_TYPEMAP_CLASS: { - zval obj; + zval obj; + zend_class_entry* obj_ce = state->odm ? state->odm : state->map.root; + +#if PHP_VERSION_ID >= 80100 + /* Enums require special handling for instantiation */ + if (obj_ce->ce_flags & ZEND_ACC_ENUM) { + int plen; + zend_bool pfree; + char* case_name; + zval* enum_case; + + case_name = php_array_fetchc_string(&state->zchild, "name", &plen, &pfree); + + if (!case_name) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Missing 'name' field to infer enum case for %s", ZSTR_VAL(obj_ce->name)); + + goto cleanup; + } + + enum_case = resolve_enum_case(obj_ce, case_name); + + if (pfree) { + efree(case_name); + } + + if (!enum_case) { + /* Exception already thrown */ + goto cleanup; + } + + ZVAL_COPY_VALUE(&obj, enum_case); + } else { + object_init_ex(&obj, obj_ce); + } +#else /* PHP_VERSION_ID < 80100 */ + object_init_ex(&obj, obj_ce); +#endif /* PHP_VERSION_ID */ - object_init_ex(&obj, state->odm ? state->odm : state->map.root); zend_call_method_with_1_params(PHONGO_COMPAT_OBJ_P(&obj), NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, &state->zchild); zval_ptr_dtor(&state->zchild); ZVAL_COPY_VALUE(&state->zchild, &obj); diff --git a/tests/bson/bson-enum-001.phpt b/tests/bson/bson-enum-001.phpt new file mode 100644 index 000000000..3facd4889 --- /dev/null +++ b/tests/bson/bson-enum-001.phpt @@ -0,0 +1,85 @@ +--TEST-- +Enums serialize as documents and are not unserialized by default +--SKIPIF-- + + +--FILE-- + MyEnum::foo], + ['myBackedEnum' => MyBackedEnum::foo], +]; + +foreach ($tests as $document) { + $bson = fromPHP($document); + echo "Test ", toJSON($bson), "\n"; + hex_dump($bson); + var_dump(toPHP($bson)); + echo "\n"; +} + +?> +===DONE=== + +--EXPECTF-- +Test { "name" : "foo" } + 0 : 13 00 00 00 02 6e 61 6d 65 00 04 00 00 00 66 6f [.....name.....fo] + 10 : 6f 00 00 [o..] +object(stdClass)#%d (%d) { + ["name"]=> + string(3) "foo" +} + +Test { "name" : "foo", "value" : "bar" } + 0 : 22 00 00 00 02 6e 61 6d 65 00 04 00 00 00 66 6f ["....name.....fo] + 10 : 6f 00 02 76 61 6c 75 65 00 04 00 00 00 62 61 72 [o..value.....bar] + 20 : 00 00 [..] +object(stdClass)#%d (%d) { + ["name"]=> + string(3) "foo" + ["value"]=> + string(3) "bar" +} + +Test { "myEnum" : { "name" : "foo" } } + 0 : 20 00 00 00 03 6d 79 45 6e 75 6d 00 13 00 00 00 [ ....myEnum.....] + 10 : 02 6e 61 6d 65 00 04 00 00 00 66 6f 6f 00 00 00 [.name.....foo...] +object(stdClass)#%d (%d) { + ["myEnum"]=> + object(stdClass)#%d (%d) { + ["name"]=> + string(3) "foo" + } +} + +Test { "myBackedEnum" : { "name" : "foo", "value" : "bar" } } + 0 : 35 00 00 00 03 6d 79 42 61 63 6b 65 64 45 6e 75 [5....myBackedEnu] + 10 : 6d 00 22 00 00 00 02 6e 61 6d 65 00 04 00 00 00 [m."....name.....] + 20 : 66 6f 6f 00 02 76 61 6c 75 65 00 04 00 00 00 62 [foo..value.....b] + 30 : 61 72 00 00 00 [ar...] +object(stdClass)#%d (%d) { + ["myBackedEnum"]=> + object(stdClass)#%d (%d) { + ["name"]=> + string(3) "foo" + ["value"]=> + string(3) "bar" + } +} + +===DONE=== diff --git a/tests/bson/bson-enum-002.phpt b/tests/bson/bson-enum-002.phpt new file mode 100644 index 000000000..208a0d86b --- /dev/null +++ b/tests/bson/bson-enum-002.phpt @@ -0,0 +1,104 @@ +--TEST-- +Enums implementing Unserializable +--SKIPIF-- + + +--FILE-- + MyEnum::class], + ], + [ + MyBackedEnum::foo, + ['root' => MyBackedEnum::class], + ], + [ + ['myEnum' => MyEnum::foo], + ['fieldPaths' => ['myEnum' => MyEnum::class]], + ], + [ + ['myBackedEnum' => MyBackedEnum::foo], + ['fieldPaths' => ['myBackedEnum' => MyBackedEnum::class]], + ], +]; + +foreach ($tests as $test) { + [$document, $typeMap] = $test; + + $bson = fromPHP($document); + echo "Test ", toJSON($bson), "\n"; + hex_dump($bson); + var_dump(toPHP($bson, $typeMap)); + echo "\n"; +} + +?> +===DONE=== + +--EXPECTF-- +Test { "name" : "foo" } + 0 : 13 00 00 00 02 6e 61 6d 65 00 04 00 00 00 66 6f [.....name.....fo] + 10 : 6f 00 00 [o..] +MyEnum::bsonUnserialize called with: {"name":"foo"} +enum(MyEnum::foo) + +Test { "name" : "foo", "value" : "bar" } + 0 : 22 00 00 00 02 6e 61 6d 65 00 04 00 00 00 66 6f ["....name.....fo] + 10 : 6f 00 02 76 61 6c 75 65 00 04 00 00 00 62 61 72 [o..value.....bar] + 20 : 00 00 [..] +MyBackedEnum::bsonUnserialize called with: {"name":"foo","value":"bar"} +enum(MyBackedEnum::foo) + +Test { "myEnum" : { "name" : "foo" } } + 0 : 20 00 00 00 03 6d 79 45 6e 75 6d 00 13 00 00 00 [ ....myEnum.....] + 10 : 02 6e 61 6d 65 00 04 00 00 00 66 6f 6f 00 00 00 [.name.....foo...] +MyEnum::bsonUnserialize called with: {"name":"foo"} +object(stdClass)#%d (%d) { + ["myEnum"]=> + enum(MyEnum::foo) +} + +Test { "myBackedEnum" : { "name" : "foo", "value" : "bar" } } + 0 : 35 00 00 00 03 6d 79 42 61 63 6b 65 64 45 6e 75 [5....myBackedEnu] + 10 : 6d 00 22 00 00 00 02 6e 61 6d 65 00 04 00 00 00 [m."....name.....] + 20 : 66 6f 6f 00 02 76 61 6c 75 65 00 04 00 00 00 62 [foo..value.....b] + 30 : 61 72 00 00 00 [ar...] +MyBackedEnum::bsonUnserialize called with: {"name":"foo","value":"bar"} +object(stdClass)#%d (%d) { + ["myBackedEnum"]=> + enum(MyBackedEnum::foo) +} + +===DONE=== diff --git a/tests/bson/bson-enum-003.phpt b/tests/bson/bson-enum-003.phpt new file mode 100644 index 000000000..bb0249982 --- /dev/null +++ b/tests/bson/bson-enum-003.phpt @@ -0,0 +1,108 @@ +--TEST-- +Enums implementing Persistable +--SKIPIF-- + + +--FILE-- + MyEnum::foo], + ['myBackedEnum' => MyBackedEnum::foo], +]; + +foreach ($tests as $document) { + $bson = fromPHP($document); + echo "Test ", toJSON($bson), "\n"; + hex_dump($bson); + var_dump(toPHP($bson)); + echo "\n"; +} + +?> +===DONE=== + +--EXPECTF-- +Test { "__pclass" : { "$binary" : "TXlFbnVt", "$type" : "80" }, "name" : "foo" } + 0 : 28 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 06 00 [(....__pclass...] + 10 : 00 00 80 4d 79 45 6e 75 6d 02 6e 61 6d 65 00 04 [...MyEnum.name..] + 20 : 00 00 00 66 6f 6f 00 00 [...foo..] +MyEnum::bsonUnserialize called with: {"__pclass":{"$binary":"TXlFbnVt","$type":"80"},"name":"foo"} +enum(MyEnum::foo) + +Test { "__pclass" : { "$binary" : "TXlCYWNrZWRFbnVt", "$type" : "80" }, "name" : "foo", "value" : "bar" } + 0 : 3d 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 0c 00 [=....__pclass...] + 10 : 00 00 80 4d 79 42 61 63 6b 65 64 45 6e 75 6d 02 [...MyBackedEnum.] + 20 : 6e 61 6d 65 00 04 00 00 00 66 6f 6f 00 02 76 61 [name.....foo..va] + 30 : 6c 75 65 00 04 00 00 00 62 61 72 00 00 [lue.....bar..] +MyBackedEnum::bsonUnserialize called with: {"__pclass":{"$binary":"TXlCYWNrZWRFbnVt","$type":"80"},"name":"foo","value":"bar"} +enum(MyBackedEnum::foo) + +Test { "myEnum" : { "__pclass" : { "$binary" : "TXlFbnVt", "$type" : "80" }, "name" : "foo" } } + 0 : 35 00 00 00 03 6d 79 45 6e 75 6d 00 28 00 00 00 [5....myEnum.(...] + 10 : 05 5f 5f 70 63 6c 61 73 73 00 06 00 00 00 80 4d [.__pclass......M] + 20 : 79 45 6e 75 6d 02 6e 61 6d 65 00 04 00 00 00 66 [yEnum.name.....f] + 30 : 6f 6f 00 00 00 [oo...] +MyEnum::bsonUnserialize called with: {"__pclass":{"$binary":"TXlFbnVt","$type":"80"},"name":"foo"} +object(stdClass)#%d (%d) { + ["myEnum"]=> + enum(MyEnum::foo) +} + +Test { "myBackedEnum" : { "__pclass" : { "$binary" : "TXlCYWNrZWRFbnVt", "$type" : "80" }, "name" : "foo", "value" : "bar" } } + 0 : 50 00 00 00 03 6d 79 42 61 63 6b 65 64 45 6e 75 [P....myBackedEnu] + 10 : 6d 00 3d 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 [m.=....__pclass.] + 20 : 0c 00 00 00 80 4d 79 42 61 63 6b 65 64 45 6e 75 [.....MyBackedEnu] + 30 : 6d 02 6e 61 6d 65 00 04 00 00 00 66 6f 6f 00 02 [m.name.....foo..] + 40 : 76 61 6c 75 65 00 04 00 00 00 62 61 72 00 00 00 [value.....bar...] +MyBackedEnum::bsonUnserialize called with: {"__pclass":{"$binary":"TXlCYWNrZWRFbnVt","$type":"80"},"name":"foo","value":"bar"} +object(stdClass)#%d (%d) { + ["myBackedEnum"]=> + enum(MyBackedEnum::foo) +} + +===DONE=== diff --git a/tests/bson/bson-enum_error-001.phpt b/tests/bson/bson-enum_error-001.phpt new file mode 100644 index 000000000..33df6644d --- /dev/null +++ b/tests/bson/bson-enum_error-001.phpt @@ -0,0 +1,80 @@ +--TEST-- +Unserialization errors for enums implementing Unserializable +--SKIPIF-- + + +--FILE-- + MyEnum::class] + ); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"name": "NOT_A_CONSTANT"}'), + ['root' => MyEnum::class] + ); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"name": "NOT_A_CASE"}'), + ['root' => MyEnum::class] + ); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"myEnum": {}}'), + ['fieldPaths' => ['myEnum' => MyEnum::class]] + ); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"myEnum": {"name": "NOT_A_CONSTANT"}}'), + ['fieldPaths' => ['myEnum' => MyEnum::class]] + ); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"myEnum": {"name": "NOT_A_CASE"}}'), + ['fieldPaths' => ['myEnum' => MyEnum::class]] + ); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Missing 'name' field to infer enum case for MyEnum +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Undefined constant MyEnum::NOT_A_CONSTANT +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MyEnum::NOT_A_CASE is not an enum case +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Missing 'name' field to infer enum case for MyEnum +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Undefined constant MyEnum::NOT_A_CONSTANT +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MyEnum::NOT_A_CASE is not an enum case +===DONE=== diff --git a/tests/bson/bson-enum_error-002.phpt b/tests/bson/bson-enum_error-002.phpt new file mode 100644 index 000000000..ce4ee43d0 --- /dev/null +++ b/tests/bson/bson-enum_error-002.phpt @@ -0,0 +1,67 @@ +--TEST-- +Unserialization errors for enums implementing Persistable +--SKIPIF-- + + +--FILE-- + +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Missing 'name' field to infer enum case for MyEnum +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Undefined constant MyEnum::NOT_A_CONSTANT +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MyEnum::NOT_A_CASE is not an enum case +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Missing 'name' field to infer enum case for MyEnum +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Undefined constant MyEnum::NOT_A_CONSTANT +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +MyEnum::NOT_A_CASE is not an enum case +===DONE=== From 4ced542bb1c80fc8a759d307cb0e0fc5c27468cf Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 24 Aug 2022 14:51:57 -0400 Subject: [PATCH 2/5] Update src/phongo_bson.c Co-authored-by: Sara Golemon --- src/phongo_bson.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phongo_bson.c b/src/phongo_bson.c index b7f534bb4..0493657d5 100644 --- a/src/phongo_bson.c +++ b/src/phongo_bson.c @@ -776,7 +776,7 @@ static void php_phongo_handle_field_path_entry_for_compound_type(php_phongo_bson static zval* resolve_enum_case(zend_class_entry* ce, const char* case_name) { zval* return_value = NULL; - zend_string* c_str; + zend_string* c_str = NULL; zend_class_constant* c; if (!(ce->ce_flags & ZEND_ACC_ENUM)) { From e5953927b5e2b945468a02535726b7d17fb511d2 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 24 Aug 2022 16:42:42 -0400 Subject: [PATCH 3/5] PersistableEnum trait --- config.m4 | 1 + config.w32 | 2 +- php_phongo.c | 1 + src/BSON/PersistableEnum.c | 49 +++++++++++++++++ src/BSON/PersistableEnum.stub.php | 15 ++++++ src/BSON/PersistableEnum_arginfo.h | 31 +++++++++++ src/phongo_classes.h | 2 + tests/bson/bson-enum-004.phpt | 81 +++++++++++++++++++++++++++++ tests/bson/bson-enum_error-003.phpt | 75 ++++++++++++++++++++++++++ 9 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/BSON/PersistableEnum.c create mode 100644 src/BSON/PersistableEnum.stub.php create mode 100644 src/BSON/PersistableEnum_arginfo.h create mode 100644 tests/bson/bson-enum-004.phpt create mode 100644 tests/bson/bson-enum_error-003.phpt diff --git a/config.m4 b/config.m4 index aca144bd1..b3b7c67bc 100644 --- a/config.m4 +++ b/config.m4 @@ -125,6 +125,7 @@ if test "$PHP_MONGODB" != "no"; then src/BSON/ObjectId.c \ src/BSON/ObjectIdInterface.c \ src/BSON/Persistable.c \ + src/BSON/PersistableEnum.c \ src/BSON/Regex.c \ src/BSON/RegexInterface.c \ src/BSON/Serializable.c \ diff --git a/config.w32 b/config.w32 index fa6b20da8..92c7beb58 100644 --- a/config.w32 +++ b/config.w32 @@ -121,7 +121,7 @@ if (PHP_MONGODB != "no") { EXTENSION("mongodb", "php_phongo.c", null, PHP_MONGODB_CFLAGS); MONGODB_ADD_SOURCES("/src", "phongo_apm.c phongo_bson.c phongo_bson_encode.c phongo_client.c phongo_compat.c phongo_error.c phongo_execute.c phongo_ini.c phongo_util.c"); - MONGODB_ADD_SOURCES("/src/BSON", "Binary.c BinaryInterface.c DBPointer.c Decimal128.c Decimal128Interface.c Int64.c Javascript.c JavascriptInterface.c MaxKey.c MaxKeyInterface.c MinKey.c MinKeyInterface.c ObjectId.c ObjectIdInterface.c Persistable.c Regex.c RegexInterface.c Serializable.c Symbol.c Timestamp.c TimestampInterface.c Type.c Undefined.c Unserializable.c UTCDateTime.c UTCDateTimeInterface.c functions.c"); + MONGODB_ADD_SOURCES("/src/BSON", "Binary.c BinaryInterface.c DBPointer.c Decimal128.c Decimal128Interface.c Int64.c Javascript.c JavascriptInterface.c MaxKey.c MaxKeyInterface.c MinKey.c MinKeyInterface.c ObjectId.c ObjectIdInterface.c Persistable.c PersistableEnum.c Regex.c RegexInterface.c Serializable.c Symbol.c Timestamp.c TimestampInterface.c Type.c Undefined.c Unserializable.c UTCDateTime.c UTCDateTimeInterface.c functions.c"); MONGODB_ADD_SOURCES("/src/MongoDB", "BulkWrite.c ClientEncryption.c Command.c Cursor.c CursorId.c CursorInterface.c Manager.c Query.c ReadConcern.c ReadPreference.c Server.c ServerApi.c ServerDescription.c Session.c TopologyDescription.c WriteConcern.c WriteConcernError.c WriteError.c WriteResult.c"); MONGODB_ADD_SOURCES("/src/MongoDB/Exception", "AuthenticationException.c BulkWriteException.c CommandException.c ConnectionException.c ConnectionTimeoutException.c EncryptionException.c Exception.c ExecutionTimeoutException.c InvalidArgumentException.c LogicException.c RuntimeException.c ServerException.c SSLConnectionException.c UnexpectedValueException.c WriteException.c"); MONGODB_ADD_SOURCES("/src/MongoDB/Monitoring", "CommandFailedEvent.c CommandStartedEvent.c CommandSubscriber.c CommandSucceededEvent.c SDAMSubscriber.c Subscriber.c ServerChangedEvent.c ServerClosedEvent.c ServerHeartbeatFailedEvent.c ServerHeartbeatStartedEvent.c ServerHeartbeatSucceededEvent.c ServerOpeningEvent.c TopologyChangedEvent.c TopologyClosedEvent.c TopologyOpeningEvent.c functions.c"); diff --git a/php_phongo.c b/php_phongo.c index e93f54c20..a12a9290a 100644 --- a/php_phongo.c +++ b/php_phongo.c @@ -217,6 +217,7 @@ PHP_MINIT_FUNCTION(mongodb) /* {{{ */ php_phongo_minkey_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_objectid_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_persistable_init_ce(INIT_FUNC_ARGS_PASSTHRU); + php_phongo_persistableenum_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_regex_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_symbol_init_ce(INIT_FUNC_ARGS_PASSTHRU); php_phongo_timestamp_init_ce(INIT_FUNC_ARGS_PASSTHRU); diff --git a/src/BSON/PersistableEnum.c b/src/BSON/PersistableEnum.c new file mode 100644 index 000000000..82bb828f9 --- /dev/null +++ b/src/BSON/PersistableEnum.c @@ -0,0 +1,49 @@ +/* + * Copyright 2014-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "php_phongo.h" +#include "phongo_error.h" +#include "PersistableEnum_arginfo.h" + +zend_class_entry* php_phongo_persistableenum_ce; + +PHP_METHOD(MongoDB_BSON_PersistableEnum, bsonSerialize) +{ + PHONGO_PARSE_PARAMETERS_NONE(); + + RETVAL_ZVAL(getThis(), 1, 0); + convert_to_array(return_value); + + return; +} + +PHP_METHOD(MongoDB_BSON_PersistableEnum, bsonUnserialize) +{ + zval* data; + + PHONGO_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(data) + PHONGO_PARSE_PARAMETERS_END(); + + return; +} /* }}} */ + +void php_phongo_persistableenum_init_ce(INIT_FUNC_ARGS) /* {{{ */ +{ + php_phongo_persistableenum_ce = register_class_MongoDB_BSON_PersistableEnum(); +} /* }}} */ diff --git a/src/BSON/PersistableEnum.stub.php b/src/BSON/PersistableEnum.stub.php new file mode 100644 index 000000000..d2f603199 --- /dev/null +++ b/src/BSON/PersistableEnum.stub.php @@ -0,0 +1,15 @@ +ce_flags |= ZEND_ACC_TRAIT; + + return class_entry; +} diff --git a/src/phongo_classes.h b/src/phongo_classes.h index 3d2a49e6e..288396a23 100644 --- a/src/phongo_classes.h +++ b/src/phongo_classes.h @@ -322,6 +322,7 @@ extern zend_class_entry* php_phongo_bulkwriteexception_ce; extern zend_class_entry* php_phongo_type_ce; extern zend_class_entry* php_phongo_persistable_ce; +extern zend_class_entry* php_phongo_persistableenum_ce; extern zend_class_entry* php_phongo_unserializable_ce; extern zend_class_entry* php_phongo_serializable_ce; extern zend_class_entry* php_phongo_binary_ce; @@ -373,6 +374,7 @@ extern void php_phongo_maxkey_init_ce(INIT_FUNC_ARGS); extern void php_phongo_minkey_init_ce(INIT_FUNC_ARGS); extern void php_phongo_objectid_init_ce(INIT_FUNC_ARGS); extern void php_phongo_persistable_init_ce(INIT_FUNC_ARGS); +extern void php_phongo_persistableenum_init_ce(INIT_FUNC_ARGS); extern void php_phongo_regex_init_ce(INIT_FUNC_ARGS); extern void php_phongo_serializable_init_ce(INIT_FUNC_ARGS); extern void php_phongo_symbol_init_ce(INIT_FUNC_ARGS); diff --git a/tests/bson/bson-enum-004.phpt b/tests/bson/bson-enum-004.phpt new file mode 100644 index 000000000..22ec37c5d --- /dev/null +++ b/tests/bson/bson-enum-004.phpt @@ -0,0 +1,81 @@ +--TEST-- +Enums implementing Persistable using PersistableEnum trait +--SKIPIF-- + + +--FILE-- + MyEnum::foo], + ['myBackedEnum' => MyBackedEnum::foo], +]; + +foreach ($tests as $document) { + $bson = fromPHP($document); + echo "Test ", toJSON($bson), "\n"; + hex_dump($bson); + var_dump(toPHP($bson)); + echo "\n"; +} + +?> +===DONE=== + +--EXPECTF-- +Test { "__pclass" : { "$binary" : "TXlFbnVt", "$type" : "80" }, "name" : "foo" } + 0 : 28 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 06 00 [(....__pclass...] + 10 : 00 00 80 4d 79 45 6e 75 6d 02 6e 61 6d 65 00 04 [...MyEnum.name..] + 20 : 00 00 00 66 6f 6f 00 00 [...foo..] +enum(MyEnum::foo) + +Test { "__pclass" : { "$binary" : "TXlCYWNrZWRFbnVt", "$type" : "80" }, "name" : "foo", "value" : "bar" } + 0 : 3d 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 0c 00 [=....__pclass...] + 10 : 00 00 80 4d 79 42 61 63 6b 65 64 45 6e 75 6d 02 [...MyBackedEnum.] + 20 : 6e 61 6d 65 00 04 00 00 00 66 6f 6f 00 02 76 61 [name.....foo..va] + 30 : 6c 75 65 00 04 00 00 00 62 61 72 00 00 [lue.....bar..] +enum(MyBackedEnum::foo) + +Test { "myEnum" : { "__pclass" : { "$binary" : "TXlFbnVt", "$type" : "80" }, "name" : "foo" } } + 0 : 35 00 00 00 03 6d 79 45 6e 75 6d 00 28 00 00 00 [5....myEnum.(...] + 10 : 05 5f 5f 70 63 6c 61 73 73 00 06 00 00 00 80 4d [.__pclass......M] + 20 : 79 45 6e 75 6d 02 6e 61 6d 65 00 04 00 00 00 66 [yEnum.name.....f] + 30 : 6f 6f 00 00 00 [oo...] +object(stdClass)#%d (%d) { + ["myEnum"]=> + enum(MyEnum::foo) +} + +Test { "myBackedEnum" : { "__pclass" : { "$binary" : "TXlCYWNrZWRFbnVt", "$type" : "80" }, "name" : "foo", "value" : "bar" } } + 0 : 50 00 00 00 03 6d 79 42 61 63 6b 65 64 45 6e 75 [P....myBackedEnu] + 10 : 6d 00 3d 00 00 00 05 5f 5f 70 63 6c 61 73 73 00 [m.=....__pclass.] + 20 : 0c 00 00 00 80 4d 79 42 61 63 6b 65 64 45 6e 75 [.....MyBackedEnu] + 30 : 6d 02 6e 61 6d 65 00 04 00 00 00 66 6f 6f 00 02 [m.name.....foo..] + 40 : 76 61 6c 75 65 00 04 00 00 00 62 61 72 00 00 00 [value.....bar...] +object(stdClass)#%d (%d) { + ["myBackedEnum"]=> + enum(MyBackedEnum::foo) +} + +===DONE=== diff --git a/tests/bson/bson-enum_error-003.phpt b/tests/bson/bson-enum_error-003.phpt new file mode 100644 index 000000000..f3345651f --- /dev/null +++ b/tests/bson/bson-enum_error-003.phpt @@ -0,0 +1,75 @@ +--TEST-- +Typemap specifies enum that does not implement Unserializable +--SKIPIF-- + + +--FILE-- + MyEnum::class] + ); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"name": "NOT_A_CONSTANT"}'), + ['root' => MyEnum::class] + ); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"name": "NOT_A_CASE"}'), + ['root' => MyEnum::class] + ); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"myEnum": {}}'), + ['fieldPaths' => ['myEnum' => MyEnum::class]] + ); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"myEnum": {"name": "NOT_A_CONSTANT"}}'), + ['fieldPaths' => ['myEnum' => MyEnum::class]] + ); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +echo throws(function() { + toPHP( + fromJSON('{"myEnum": {"name": "NOT_A_CASE"}}'), + ['fieldPaths' => ['myEnum' => MyEnum::class]] + ); +}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyEnum does not implement MongoDB\BSON\Unserializable +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyEnum does not implement MongoDB\BSON\Unserializable +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyEnum does not implement MongoDB\BSON\Unserializable +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyEnum does not implement MongoDB\BSON\Unserializable +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyEnum does not implement MongoDB\BSON\Unserializable +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyEnum does not implement MongoDB\BSON\Unserializable +===DONE=== From 7152d5e5ec00fc0e72e687c51ba51a0181ef1e9a Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 24 Aug 2022 20:47:22 -0400 Subject: [PATCH 4/5] Copy enum cases with ZVAL_COPY to increment refcount --- src/phongo_bson.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/phongo_bson.c b/src/phongo_bson.c index 0493657d5..f49a31b20 100644 --- a/src/phongo_bson.c +++ b/src/phongo_bson.c @@ -891,7 +891,7 @@ static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, c return true; } - ZVAL_COPY_VALUE(&obj, enum_case); + ZVAL_COPY(&obj, enum_case); } else { object_init_ex(&obj, obj_ce); } @@ -1156,7 +1156,7 @@ bool php_phongo_bson_to_zval_ex(const unsigned char* data, int data_len, php_pho goto cleanup; } - ZVAL_COPY_VALUE(&obj, enum_case); + ZVAL_COPY(&obj, enum_case); } else { object_init_ex(&obj, obj_ce); } From d6c019e6bc305c0981b9798f3be5dd1b4d851fd7 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 24 Aug 2022 21:44:38 -0400 Subject: [PATCH 5/5] Fix code formatting --- src/phongo_bson.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phongo_bson.c b/src/phongo_bson.c index f49a31b20..cc2ad3eec 100644 --- a/src/phongo_bson.c +++ b/src/phongo_bson.c @@ -776,7 +776,7 @@ static void php_phongo_handle_field_path_entry_for_compound_type(php_phongo_bson static zval* resolve_enum_case(zend_class_entry* ce, const char* case_name) { zval* return_value = NULL; - zend_string* c_str = NULL; + zend_string* c_str = NULL; zend_class_constant* c; if (!(ce->ce_flags & ZEND_ACC_ENUM)) {