diff --git a/config.m4 b/config.m4 index 4c7d3b6fc..5fe263336 100644 --- a/config.m4 +++ b/config.m4 @@ -125,7 +125,6 @@ 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 92c7beb58..fa6b20da8 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 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/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/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 06eab88f7..137f25979 100644 --- a/php_phongo.c +++ b/php_phongo.c @@ -216,7 +216,6 @@ 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/Persistable.c b/src/BSON/Persistable.c index 91003ffa1..c47a3cc8c 100644 --- a/src/BSON/Persistable.c +++ b/src/BSON/Persistable.c @@ -21,7 +21,20 @@ zend_class_entry* php_phongo_persistable_ce; +static int php_phongo_implement_persistable(zend_class_entry* interface, zend_class_entry* class_type) +{ +#if PHP_VERSION_ID >= 80100 + if (class_type->ce_flags & ZEND_ACC_ENUM) { + zend_error_noreturn(E_ERROR, "Enum class %s cannot implement interface %s", ZSTR_VAL(class_type->name), ZSTR_VAL(interface->name)); + return FAILURE; + } +#endif /* PHP_VERSION_ID >= 80100 */ + + return SUCCESS; +} + void php_phongo_persistable_init_ce(INIT_FUNC_ARGS) { - php_phongo_persistable_ce = register_class_MongoDB_BSON_Persistable(php_phongo_serializable_ce, php_phongo_unserializable_ce); + php_phongo_persistable_ce = register_class_MongoDB_BSON_Persistable(php_phongo_serializable_ce, php_phongo_unserializable_ce); + php_phongo_persistable_ce->interface_gets_implemented = php_phongo_implement_persistable; } diff --git a/src/BSON/PersistableEnum.c b/src/BSON/PersistableEnum.c deleted file mode 100644 index e703dc679..000000000 --- a/src/BSON/PersistableEnum.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 deleted file mode 100644 index d0dc4ca73..000000000 --- a/src/BSON/PersistableEnum.stub.php +++ /dev/null @@ -1,15 +0,0 @@ -ce_flags |= ZEND_ACC_TRAIT; - - return class_entry; -} diff --git a/src/BSON/Unserializable.c b/src/BSON/Unserializable.c index f430be323..c31910b75 100644 --- a/src/BSON/Unserializable.c +++ b/src/BSON/Unserializable.c @@ -21,7 +21,20 @@ zend_class_entry* php_phongo_unserializable_ce; +static int php_phongo_implement_unserializable(zend_class_entry* interface, zend_class_entry* class_type) +{ +#if PHP_VERSION_ID >= 80100 + if (class_type->ce_flags & ZEND_ACC_ENUM) { + zend_error_noreturn(E_ERROR, "Enum class %s cannot implement interface %s", ZSTR_VAL(class_type->name), ZSTR_VAL(interface->name)); + return FAILURE; + } +#endif /* PHP_VERSION_ID >= 80100 */ + + return SUCCESS; +} + void php_phongo_unserializable_init_ce(INIT_FUNC_ARGS) { - php_phongo_unserializable_ce = register_class_MongoDB_BSON_Unserializable(); + php_phongo_unserializable_ce = register_class_MongoDB_BSON_Unserializable(); + php_phongo_unserializable_ce->interface_gets_implemented = php_phongo_implement_unserializable; } diff --git a/src/phongo_bson.c b/src/phongo_bson.c index a3e1fb8a1..dd8b301b7 100644 --- a/src/phongo_bson.c +++ b/src/phongo_bson.c @@ -17,6 +17,9 @@ #include "bson/bson.h" #include +#if PHP_VERSION_ID >= 80100 +#include +#endif #include #include "php_array_api.h" @@ -28,9 +31,6 @@ #undef MONGOC_LOG_DOMAIN #define MONGOC_LOG_DOMAIN "PHONGO-BSON" -#define PHONGO_IS_CLASS_INSTANTIATABLE(ce) \ - (!(ce->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) - #define PHONGO_BSON_STATE_ZCHILD(state) (&((php_phongo_bson_state*) (state))->zchild) #define PHONGO_FIELD_PATH_EXPANSION 8 @@ -39,6 +39,21 @@ static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, const char* key, const bson_t* v_document, void* data); static bool php_phongo_bson_visit_array(const bson_iter_t* iter ARG_UNUSED, const char* key, const bson_t* v_array, void* data); +static inline bool phongo_is_class_instantiatable(const zend_class_entry* ce) +{ + if (ce->ce_flags & (ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) { + return false; + } + +#if PHP_VERSION_ID >= 80100 + if (ce->ce_flags & ZEND_ACC_ENUM) { + return false; + } +#endif /* PHP_VERSION_ID < 80100 */ + + return true; +} + /* Path builder */ char* php_phongo_field_path_as_string(php_phongo_field_path* field_path) { @@ -267,7 +282,7 @@ static bool php_phongo_bson_visit_binary(const bson_iter_t* iter ARG_UNUSED, con zend_class_entry* found_ce = zend_fetch_class(zs_classname, ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_SILENT); zend_string_release(zs_classname); - if (found_ce && PHONGO_IS_CLASS_INSTANTIATABLE(found_ce) && instanceof_function(found_ce, php_phongo_persistable_ce)) { + if (found_ce && phongo_is_class_instantiatable(found_ce) && instanceof_function(found_ce, php_phongo_persistable_ce)) { ((php_phongo_bson_state*) data)->odm = found_ce; } } @@ -768,95 +783,6 @@ 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 = NULL; - 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_init_document_object(zval* src, zend_class_entry* obj_ce, zval* obj) -{ -#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(src, "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)); - - return false; - } - - enum_case = resolve_enum_case(obj_ce, case_name); - - if (pfree) { - efree(case_name); - } - - if (!enum_case) { - /* Exception already thrown */ - return false; - } - - ZVAL_COPY(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 */ - - return true; -} - 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); @@ -903,18 +829,12 @@ static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, c zval obj; zend_class_entry* obj_ce = state.odm ? state.odm : state.map.document.ce; - if (!php_phongo_bson_init_document_object(&state.zchild, obj_ce, &obj)) { - /* 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; - } + object_init_ex(&obj, obj_ce); 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); + break; } @@ -1127,40 +1047,7 @@ bool php_phongo_bson_to_zval_ex(const bson_t* b, php_phongo_bson_state* state) zval obj; zend_class_entry* obj_ce = state->odm ? state->odm : state->map.root.ce; -#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(&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 */ 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); @@ -1239,8 +1126,8 @@ static zend_class_entry* php_phongo_bson_state_fetch_class(const char* classname if (!found_ce) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Class %s does not exist", classname); - } else if (!PHONGO_IS_CLASS_INSTANTIATABLE(found_ce)) { - phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Class %s is not instantiatable", classname); + } else if (!phongo_is_class_instantiatable(found_ce)) { + phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s %s is not instantiatable", zend_get_object_type_uc(found_ce), classname); } else if (!instanceof_function(found_ce, interface_ce)) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Class %s does not implement %s", classname, ZSTR_VAL(interface_ce->name)); } else { @@ -1276,6 +1163,7 @@ static bool php_phongo_bson_state_parse_type(zval* options, const char* name, ph if ((element->ce = php_phongo_bson_state_fetch_class(classname, classname_len, php_phongo_unserializable_ce))) { element->type = PHONGO_TYPEMAP_CLASS; } else { + /* Exception already thrown */ retval = false; } } diff --git a/src/phongo_bson_encode.c b/src/phongo_bson_encode.c index c23601fb6..7300806bc 100644 --- a/src/phongo_bson_encode.c +++ b/src/phongo_bson_encode.c @@ -17,6 +17,9 @@ #include "bson/bson.h" #include +#if PHP_VERSION_ID >= 80100 +#include +#endif #include #include "php_phongo.h" @@ -43,6 +46,7 @@ #endif /* Forwards declarations */ +static void php_phongo_bson_append(bson_t* bson, php_phongo_field_path* field_path, php_phongo_bson_flags_t flags, const char* key, long key_len, zval* entry); static void php_phongo_zval_to_bson_internal(zval* data, php_phongo_field_path* field_path, php_phongo_bson_flags_t flags, bson_t* bson, bson_t** bson_out); /* Determines whether the argument should be serialized as a BSON array or @@ -236,7 +240,23 @@ static void php_phongo_bson_append_object(bson_t* bson, php_phongo_field_path* f phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Unexpected %s instance: %s", ZSTR_VAL(php_phongo_type_ce->name), ZSTR_VAL(Z_OBJCE_P(object)->name)); return; - } else { + } + +#if PHP_VERSION_ID >= 80100 + if (Z_TYPE_P(object) == IS_OBJECT && Z_OBJCE_P(object)->ce_flags & ZEND_ACC_ENUM) { + if (Z_OBJCE_P(object)->enum_backing_type == IS_UNDEF) { + char* path_string = php_phongo_field_path_as_string(field_path); + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Non-backed enum %s cannot be serialized for field path \"%s\"", ZSTR_VAL(Z_OBJCE_P(object)->name), path_string); + efree(path_string); + return; + } + + php_phongo_bson_append(bson, field_path, flags, key, key_len, zend_enum_fetch_case_value(Z_OBJ_P(object))); + return; + } +#endif /* PHP_VERSION_ID >= 80100 */ + + { bson_t child; mongoc_log(MONGOC_LOG_LEVEL_TRACE, MONGOC_LOG_DOMAIN, "encoding document"); @@ -389,6 +409,16 @@ static void php_phongo_zval_to_bson_internal(zval* data, php_phongo_field_path* break; } + /* For the error handling that follows, we can safely assume that we + * are at the root level, since php_phongo_bson_append_object would + * have already been called for a non-root level. */ +#if PHP_VERSION_ID >= 80100 + if (Z_OBJCE_P(data)->ce_flags & ZEND_ACC_ENUM) { + phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Enum %s cannot be serialized as a root element", ZSTR_VAL(Z_OBJCE_P(data)->name)); + return; + } +#endif /* PHP_VERSION_ID >= 80100 */ + if (instanceof_function(Z_OBJCE_P(data), php_phongo_type_ce)) { phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "%s instance %s cannot be serialized as a root element", ZSTR_VAL(php_phongo_type_ce->name), ZSTR_VAL(Z_OBJCE_P(data)->name)); return; diff --git a/src/phongo_classes.h b/src/phongo_classes.h index 288396a23..3d2a49e6e 100644 --- a/src/phongo_classes.h +++ b/src/phongo_classes.h @@ -322,7 +322,6 @@ 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; @@ -374,7 +373,6 @@ 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/src/phongo_compat.c b/src/phongo_compat.c index 08cf9d104..df1e5a860 100644 --- a/src/phongo_compat.c +++ b/src/phongo_compat.c @@ -16,6 +16,8 @@ #include +#include "phongo_compat.h" + #ifdef ZEND_HASH_GET_APPLY_COUNT /* PHP 7.2 or earlier recursion protection */ zend_bool php_phongo_zend_hash_apply_protection_begin(HashTable* ht) { @@ -44,7 +46,7 @@ zend_bool php_phongo_zend_hash_apply_protection_end(HashTable* ht) } return 1; } -#else /* PHP 7.3 or later */ +#else /* PHP 7.3 or later */ zend_bool php_phongo_zend_hash_apply_protection_begin(zend_array* ht) { if (GC_IS_RECURSIVE(ht)) { @@ -66,4 +68,22 @@ zend_bool php_phongo_zend_hash_apply_protection_end(zend_array* ht) } return 1; } -#endif +#endif /* ZEND_HASH_GET_APPLY_COUNT */ + +#if PHP_VERSION_ID < 80200 +const char* zend_get_object_type_case(const zend_class_entry* ce, zend_bool upper_case) +{ + if (ce->ce_flags & ZEND_ACC_TRAIT) { + return upper_case ? "Trait" : "trait"; + } + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + return upper_case ? "Interface" : "interface"; + } +#if PHP_VERSION_ID >= 80100 + if (ce->ce_flags & ZEND_ACC_ENUM) { + return upper_case ? "Enum" : "enum"; + } +#endif /* PHP_VERSION_ID > 80100 */ + return upper_case ? "Class" : "class"; +} +#endif /* PHP_VERSION_ID < 80200 */ diff --git a/src/phongo_compat.h b/src/phongo_compat.h index 6dd877dc6..f64729098 100644 --- a/src/phongo_compat.h +++ b/src/phongo_compat.h @@ -315,4 +315,11 @@ static inline zend_bool zend_ini_parse_bool(zend_string* str) zend_bool php_phongo_zend_hash_apply_protection_begin(HashTable* ht); zend_bool php_phongo_zend_hash_apply_protection_end(HashTable* ht); +/* zend_get_object_type_case functions were introduced in PHP 8.2 */ +#if PHP_VERSION_ID < 80200 +const char* zend_get_object_type_case(const zend_class_entry* ce, zend_bool upper_case); +#define zend_get_object_type(ce) zend_get_object_type_case((ce), false) +#define zend_get_object_type_uc(ce) zend_get_object_type_case((ce), true) +#endif /* PHP_VERSION_ID < 80200 */ + #endif /* PHONGO_COMPAT_H */ diff --git a/tests/bson/bson-enum-001.phpt b/tests/bson/bson-enum-001.phpt index 3facd4889..1374d2013 100644 --- a/tests/bson/bson-enum-001.phpt +++ b/tests/bson/bson-enum-001.phpt @@ -1,5 +1,5 @@ --TEST-- -Enums serialize as documents and are not unserialized by default +Backed enums serialize as their case value --SKIPIF-- @@ -8,78 +8,26 @@ Enums serialize as documents and are not unserialized by default require_once __DIR__ . '/../utils/basic.inc'; -enum MyEnum +enum MyIntBackedEnum: int { - case foo; + case A = 1; } -enum MyBackedEnum: string +enum MyStringBackedEnum: string { - case foo = 'bar'; + case A = 'a'; } -$tests = [ - MyEnum::foo, - MyBackedEnum::foo, - ['myEnum' => MyEnum::foo], - ['myBackedEnum' => MyBackedEnum::foo], +$document = [ + 'x' => MyIntBackedEnum::A, + 'y' => MyStringBackedEnum::A, ]; -foreach ($tests as $document) { - $bson = fromPHP($document); - echo "Test ", toJSON($bson), "\n"; - hex_dump($bson); - var_dump(toPHP($bson)); - echo "\n"; -} +echo toCanonicalExtendedJSON(fromPHP($document)), "\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" - } -} - +{ "x" : { "$numberInt" : "1" }, "y" : "a" } ===DONE=== diff --git a/tests/bson/bson-enum-002.phpt b/tests/bson/bson-enum-002.phpt index 208a0d86b..af6943f66 100644 --- a/tests/bson/bson-enum-002.phpt +++ b/tests/bson/bson-enum-002.phpt @@ -1,5 +1,5 @@ --TEST-- -Enums implementing Unserializable +Backed enums round-tripped through parent document --SKIPIF-- @@ -8,97 +8,55 @@ Enums implementing Unserializable require_once __DIR__ . '/../utils/basic.inc'; -enum MyEnum implements MongoDB\BSON\Unserializable +enum MyIntBackedEnum: int { - case foo; + case A = 1; +} - public function bsonUnserialize(array $data): void - { - /* Enums do not maintain state, so this method serves no practical - * purpose other than being required by the interface. Since an - * implementation is required, we will log a message to assert that BSON - * decoding always invokes this method. */ - printf("%s called with: %s\n", __METHOD__, json_encode($data)); - } +enum MyStringBackedEnum: string +{ + case A = 'a'; } -enum MyBackedEnum: string implements MongoDB\BSON\Unserializable +class MyDocument implements MongoDB\BSON\Persistable { - case foo = 'bar'; + public function __construct( + private MyIntBackedEnum $x, + private MyStringBackedEnum $y + ) {} + + public function bsonSerialize(): array + { + return [ + 'x' => $this->x, + 'y' => $this->y, + ]; + } public function bsonUnserialize(array $data): void { - printf("%s called with: %s\n", __METHOD__, json_encode($data)); + if (isset($data['x'])) { + $this->x = MyIntBackedEnum::from($data['x']); + } + + if (isset($data['y'])) { + $this->y = MyStringBackedEnum::from($data['y']); + } } } -$tests = [ - /* There is no practical reason to use an enum as a root document, since it - * cannot have an "_id" field, but we'll test this anyway since we're only - * using BSON functions and not round-tripping data through the server. */ - [ - MyEnum::foo, - ['root' => 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; +$document = new MyDocument(MyIntBackedEnum::A, MyStringBackedEnum::A); - $bson = fromPHP($document); - echo "Test ", toJSON($bson), "\n"; - hex_dump($bson); - var_dump(toPHP($bson, $typeMap)); - echo "\n"; -} +var_dump(toPHP(fromPHP($document))); ?> ===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) +object(MyDocument)#%d (%d) { + ["x":"MyDocument":private]=> + enum(MyIntBackedEnum::A) + ["y":"MyDocument":private]=> + enum(MyStringBackedEnum::A) } - ===DONE=== diff --git a/tests/bson/bson-enum-003.phpt b/tests/bson/bson-enum-003.phpt index bb0249982..1fff17d65 100644 --- a/tests/bson/bson-enum-003.phpt +++ b/tests/bson/bson-enum-003.phpt @@ -1,5 +1,5 @@ --TEST-- -Enums implementing Persistable +Enums implementing Serializable round-tripped through parent document --SKIPIF-- @@ -8,101 +8,70 @@ Enums implementing Persistable require_once __DIR__ . '/../utils/basic.inc'; -enum MyEnum implements MongoDB\BSON\Persistable +/* Although non-backed enums cannot be serialized directly, they can implement + * Serializable like any other object. This allows a non-backed enum to be + * round-tripped in conjunction with a parent document's bsonUnserialize() + * method. */ +enum MyEnum implements MongoDB\BSON\Serializable { - case foo; + case A; public function bsonSerialize(): array { - /* Casting the enum to an array is necessary to ensure its properties - * are serialized to a BSON document. The "name" property, common to - * both pure and backed enums, will be required for unserialization. */ return (array) $this; } - - public function bsonUnserialize(array $data): void +} + +enum MyBackedEnum: string implements MongoDB\BSON\Serializable +{ + case A = 'a'; + + public function bsonSerialize(): array { - /* Enums do not maintain state, so this method serves no practical - * purpose other than being required by the interface. Since an - * implementation is required, we will log a message to assert that BSON - * decoding always invokes this method. */ - printf("%s called with: %s\n", __METHOD__, json_encode($data)); + return (array) $this; } } -enum MyBackedEnum: string implements MongoDB\BSON\Persistable +class MyDocument implements MongoDB\BSON\Persistable { - case foo = 'bar'; + public function __construct( + private MyEnum $x, + private MyBackedEnum $y + ) {} public function bsonSerialize(): array { - return (array) $this; + return [ + 'x' => $this->x, + 'y' => $this->y, + ]; } - + public function bsonUnserialize(array $data): void { - printf("%s called with: %s\n", __METHOD__, json_encode($data)); + if (isset($data['x'])) { + // See: https://www.php.net/manual/en/language.enumerations.basics.php#127112 + $this->x = constant(MyEnum::class . '::' . $data['x']->name); + } + + if (isset($data['y'])) { + $this->y = MyBackedEnum::from($data['y']->value); + } } } -$tests = [ - /* There is no practical reason to use an enum as a root document, since it - * cannot have an "_id" field, but we'll test this anyway since we're only - * using BSON functions and not round-tripping data through the server. */ - MyEnum::foo, - MyBackedEnum::foo, - ['myEnum' => MyEnum::foo], - ['myBackedEnum' => MyBackedEnum::foo], -]; +$document = new MyDocument(MyEnum::A, MyBackedEnum::A); -foreach ($tests as $document) { - $bson = fromPHP($document); - echo "Test ", toJSON($bson), "\n"; - hex_dump($bson); - var_dump(toPHP($bson)); - echo "\n"; -} +var_dump(toPHP(fromPHP($document))); ?> ===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) +object(MyDocument)#%d (%d) { + ["x":"MyDocument":private]=> + enum(MyEnum::A) + ["y":"MyDocument":private]=> + enum(MyBackedEnum::A) } - ===DONE=== diff --git a/tests/bson/bson-enum-004.phpt b/tests/bson/bson-enum-004.phpt deleted file mode 100644 index 22ec37c5d..000000000 --- a/tests/bson/bson-enum-004.phpt +++ /dev/null @@ -1,81 +0,0 @@ ---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-001.phpt b/tests/bson/bson-enum_error-001.phpt index 33df6644d..bcc6c8a8a 100644 --- a/tests/bson/bson-enum_error-001.phpt +++ b/tests/bson/bson-enum_error-001.phpt @@ -1,80 +1,20 @@ --TEST-- -Unserialization errors for enums implementing Unserializable +Enum cannot implement 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=== +--EXPECTF-- +Fatal error: Enum class MyEnum cannot implement interface MongoDB\BSON\Unserializable in %s on line %d diff --git a/tests/bson/bson-enum_error-002.phpt b/tests/bson/bson-enum_error-002.phpt index ce4ee43d0..139dd1207 100644 --- a/tests/bson/bson-enum_error-002.phpt +++ b/tests/bson/bson-enum_error-002.phpt @@ -1,67 +1,20 @@ --TEST-- -Unserialization errors for enums implementing Persistable +Backed enum cannot implement Unserializable --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=== +--EXPECTF-- +Fatal error: Enum class MyBackedEnum cannot implement interface MongoDB\BSON\Unserializable in %s on line %d diff --git a/tests/bson/bson-enum_error-003.phpt b/tests/bson/bson-enum_error-003.phpt index f3345651f..77939732c 100644 --- a/tests/bson/bson-enum_error-003.phpt +++ b/tests/bson/bson-enum_error-003.phpt @@ -1,75 +1,22 @@ --TEST-- -Typemap specifies enum that does not implement Unserializable +Enum cannot implement Persistable --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"; + case A; -echo throws(function() { - toPHP( - fromJSON('{"name": "NOT_A_CASE"}'), - ['root' => MyEnum::class] - ); -}, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n"; + public function bsonSerialize(): array {} -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"; + public function bsonUnserialize(array $data): void {} +} ?> ===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=== +--EXPECTF-- +Fatal error: Enum class MyEnum cannot implement interface MongoDB\BSON\Persistable in %s on line %d diff --git a/tests/bson/bson-enum_error-004.phpt b/tests/bson/bson-enum_error-004.phpt new file mode 100644 index 000000000..37d1d58a8 --- /dev/null +++ b/tests/bson/bson-enum_error-004.phpt @@ -0,0 +1,22 @@ +--TEST-- +Backed enum cannot implement Persistable +--SKIPIF-- + + +--FILE-- + +===DONE=== + +--EXPECTF-- +Fatal error: Enum class MyBackedEnum cannot implement interface MongoDB\BSON\Persistable in %s on line %d diff --git a/tests/bson/bson-enum_error-005.phpt b/tests/bson/bson-enum_error-005.phpt new file mode 100644 index 000000000..67b31cf63 --- /dev/null +++ b/tests/bson/bson-enum_error-005.phpt @@ -0,0 +1,37 @@ +--TEST-- +Enums cannot be serialized as a root element +--SKIPIF-- + + +--FILE-- + +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Enum MyEnum cannot be serialized as a root element +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Enum MyBackedEnum cannot be serialized as a root element +===DONE=== diff --git a/tests/bson/bson-enum_error-006.phpt b/tests/bson/bson-enum_error-006.phpt new file mode 100644 index 000000000..8b8e4b57f --- /dev/null +++ b/tests/bson/bson-enum_error-006.phpt @@ -0,0 +1,26 @@ +--TEST-- +Non-backed enums cannot be serialized +--SKIPIF-- + + +--FILE-- + MyEnum::A]); +}, MongoDB\Driver\Exception\UnexpectedValueException::class), "\n"; + +?> +===DONE=== + +--EXPECT-- +OK: Got MongoDB\Driver\Exception\UnexpectedValueException +Non-backed enum MyEnum cannot be serialized for field path "x" +===DONE=== diff --git a/tests/bson/bson-toPHP-003.phpt b/tests/bson/bson-toPHP-003.phpt index c43102fb0..d2045411d 100644 --- a/tests/bson/bson-toPHP-003.phpt +++ b/tests/bson/bson-toPHP-003.phpt @@ -268,7 +268,7 @@ Class MyClass does not implement MongoDB\BSON\Unserializable === IS NOT A CONCRETE CLASS === { "foo": "yes" } -Class MongoDB\BSON\Unserializable is not instantiatable +Interface MongoDB\BSON\Unserializable is not instantiatable === IS NOT A CONCRETE CLASS VIA PCLASS === diff --git a/tests/bson/bson-toPHP-013.phpt b/tests/bson/bson-toPHP-013.phpt new file mode 100644 index 000000000..617895e6e --- /dev/null +++ b/tests/bson/bson-toPHP-013.phpt @@ -0,0 +1,100 @@ +--TEST-- +Uninstantiatable classes are ignored when processing __pclass +--FILE-- + +===DONE=== + +--EXPECTF-- +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(12) "MissingClass" + ["type"]=> + int(128) + } + ["y"]=> + int(1) + } +} +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(18) "MyAbstractDocument" + ["type"]=> + int(128) + } + ["y"]=> + int(1) + } +} +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(10) "MyDocument" + ["type"]=> + int(128) + } + ["y"]=> + int(1) + } +} +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(7) "MyTrait" + ["type"]=> + int(128) + } + ["y"]=> + int(1) + } +} +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(24) "MongoDB\BSON\Persistable" + ["type"]=> + int(128) + } + ["y"]=> + int(1) + } +} +===DONE=== diff --git a/tests/bson/bson-toPHP-014.phpt b/tests/bson/bson-toPHP-014.phpt new file mode 100644 index 000000000..b1a20a534 --- /dev/null +++ b/tests/bson/bson-toPHP-014.phpt @@ -0,0 +1,62 @@ +--TEST-- +Uninstantiatable classes are ignored when processing __pclass (enums) +--SKIPIF-- + + +--FILE-- + +===DONE=== + +--EXPECTF-- +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(6) "MyEnum" + ["type"]=> + int(128) + } + ["name"]=> + string(1) "A" + } +} +object(stdClass)#%d (%d) { + ["x"]=> + object(stdClass)#%d (%d) { + ["__pclass"]=> + object(MongoDB\BSON\Binary)#%d (%d) { + ["data"]=> + string(12) "MyBackedEnum" + ["type"]=> + int(128) + } + ["name"]=> + string(1) "A" + ["value"]=> + int(1) + } +} +===DONE=== diff --git a/tests/bson/bson-toPHP_error-001.phpt b/tests/bson/bson-toPHP_error-001.phpt index fe237df40..e7792f04e 100644 --- a/tests/bson/bson-toPHP_error-001.phpt +++ b/tests/bson/bson-toPHP_error-001.phpt @@ -1,5 +1,5 @@ --TEST-- -MongoDB\BSON\toPHP(): Type classes must be instantiatable and implement Unserializable +MongoDB\BSON\toPHP(): Type map classes must be instantiatable and implement Unserializable --FILE-- $class]; +foreach ($classes as $class) { + $typeMaps = [ + ['array' => $class], + ['document' => $class], + ['root' => $class], + ['fieldPaths' => ['x' => $class]], + ]; + foreach ($typeMaps as $typeMap) { printf("Test typeMap: %s\n", json_encode($typeMap)); - echo throws(function() use ($bson, $typeMap) { - toPHP($bson, $typeMap); - }, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n"; - - echo "\n"; + echo throws(function() use ($typeMap) { + toPHP(fromJSON('{}'), $typeMap); + }, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n\n"; } } @@ -46,48 +44,80 @@ Test typeMap: {"array":"MissingClass"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException Class MissingClass does not exist -Test typeMap: {"array":"MyAbstractDocument"} +Test typeMap: {"document":"MissingClass"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyAbstractDocument is not instantiatable +Class MissingClass does not exist -Test typeMap: {"array":"MyDocument"} +Test typeMap: {"root":"MissingClass"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyDocument does not implement MongoDB\BSON\Unserializable +Class MissingClass does not exist -Test typeMap: {"array":"MongoDB\\BSON\\Unserializable"} +Test typeMap: {"fieldPaths":{"x":"MissingClass"}} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable +Class MissingClass does not exist -Test typeMap: {"document":"MissingClass"} +Test typeMap: {"array":"MyAbstractDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MissingClass does not exist +Class MyAbstractDocument is not instantiatable Test typeMap: {"document":"MyAbstractDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException Class MyAbstractDocument is not instantiatable -Test typeMap: {"document":"MyDocument"} +Test typeMap: {"root":"MyAbstractDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyDocument does not implement MongoDB\BSON\Unserializable +Class MyAbstractDocument is not instantiatable -Test typeMap: {"document":"MongoDB\\BSON\\Unserializable"} +Test typeMap: {"fieldPaths":{"x":"MyAbstractDocument"}} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable +Class MyAbstractDocument is not instantiatable -Test typeMap: {"root":"MissingClass"} +Test typeMap: {"array":"MyDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MissingClass does not exist +Class MyDocument does not implement MongoDB\BSON\Unserializable -Test typeMap: {"root":"MyAbstractDocument"} +Test typeMap: {"document":"MyDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyAbstractDocument is not instantiatable +Class MyDocument does not implement MongoDB\BSON\Unserializable Test typeMap: {"root":"MyDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException Class MyDocument does not implement MongoDB\BSON\Unserializable +Test typeMap: {"fieldPaths":{"x":"MyDocument"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyDocument does not implement MongoDB\BSON\Unserializable + +Test typeMap: {"array":"MyTrait"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Trait MyTrait is not instantiatable + +Test typeMap: {"document":"MyTrait"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Trait MyTrait is not instantiatable + +Test typeMap: {"root":"MyTrait"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Trait MyTrait is not instantiatable + +Test typeMap: {"fieldPaths":{"x":"MyTrait"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Trait MyTrait is not instantiatable + +Test typeMap: {"array":"MongoDB\\BSON\\Unserializable"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Interface MongoDB\BSON\Unserializable is not instantiatable + +Test typeMap: {"document":"MongoDB\\BSON\\Unserializable"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Interface MongoDB\BSON\Unserializable is not instantiatable + Test typeMap: {"root":"MongoDB\\BSON\\Unserializable"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable +Interface MongoDB\BSON\Unserializable is not instantiatable + +Test typeMap: {"fieldPaths":{"x":"MongoDB\\BSON\\Unserializable"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Interface MongoDB\BSON\Unserializable is not instantiatable ===DONE=== diff --git a/tests/bson/bson-toPHP_error-007.phpt b/tests/bson/bson-toPHP_error-007.phpt new file mode 100644 index 000000000..9be7ee0f9 --- /dev/null +++ b/tests/bson/bson-toPHP_error-007.phpt @@ -0,0 +1,79 @@ +--TEST-- +MongoDB\BSON\toPHP(): Type map classes must be instantiatable and implement Unserializable (enums) +--SKIPIF-- + + +--FILE-- + $class], + ['document' => $class], + ['root' => $class], + ['fieldPaths' => ['x' => $class]], + ]; + + foreach ($typeMaps as $typeMap) { + printf("Test typeMap: %s\n", json_encode($typeMap)); + + echo throws(function() use ($typeMap) { + toPHP(fromJSON('{}'), $typeMap); + }, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n\n"; + } +} + +?> +===DONE=== + +--EXPECT-- +Test typeMap: {"array":"MyEnum"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyEnum is not instantiatable + +Test typeMap: {"document":"MyEnum"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyEnum is not instantiatable + +Test typeMap: {"root":"MyEnum"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyEnum is not instantiatable + +Test typeMap: {"fieldPaths":{"x":"MyEnum"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyEnum is not instantiatable + +Test typeMap: {"array":"MyBackedEnum"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyBackedEnum is not instantiatable + +Test typeMap: {"document":"MyBackedEnum"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyBackedEnum is not instantiatable + +Test typeMap: {"root":"MyBackedEnum"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyBackedEnum is not instantiatable + +Test typeMap: {"fieldPaths":{"x":"MyBackedEnum"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Enum MyBackedEnum is not instantiatable + +===DONE=== diff --git a/tests/cursor/cursor-setTypeMap_error-001.phpt b/tests/cursor/cursor-setTypeMap_error-001.phpt index a39026721..a2cfb32f5 100644 --- a/tests/cursor/cursor-setTypeMap_error-001.phpt +++ b/tests/cursor/cursor-setTypeMap_error-001.phpt @@ -1,5 +1,5 @@ --TEST-- -Cursor::setTypeMap(): Type classes must be instantiatable and implement Unserializable +Cursor::setTypeMap(): Type map classes must be instantiatable and implement Unserializable --SKIPIF-- @@ -13,33 +13,32 @@ abstract class MyAbstractDocument implements MongoDB\BSON\Unserializable {} class MyDocument {} -$types = [ - 'array', - 'document', - 'root', -]; - +/* Note: this test omits traits and enums, although those are tested with + * MongoDB\BSON\toPHP(), which uses the same type map validation. */ $classes = [ 'MissingClass', - 'MyAbstractDocument', - 'MyDocument', - 'MongoDB\BSON\Unserializable', + MyAbstractDocument::class, + MyDocument::class, + MongoDB\BSON\Unserializable::class, ]; $manager = create_test_manager(); $cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); -foreach ($types as $type) { - foreach ($classes as $class) { - $typeMap = [$type => $class]; +foreach ($classes as $class) { + $typeMaps = [ + ['array' => $class], + ['document' => $class], + ['root' => $class], + ['fieldPaths' => ['x' => $class]], + ]; + foreach ($typeMaps as $typeMap) { printf("Test typeMap: %s\n", json_encode($typeMap)); echo throws(function() use ($cursor, $typeMap) { $cursor->setTypeMap($typeMap); - }, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n"; - - echo "\n"; + }, MongoDB\Driver\Exception\InvalidArgumentException::class), "\n\n"; } } @@ -51,48 +50,64 @@ Test typeMap: {"array":"MissingClass"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException Class MissingClass does not exist -Test typeMap: {"array":"MyAbstractDocument"} +Test typeMap: {"document":"MissingClass"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyAbstractDocument is not instantiatable +Class MissingClass does not exist -Test typeMap: {"array":"MyDocument"} +Test typeMap: {"root":"MissingClass"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyDocument does not implement MongoDB\BSON\Unserializable +Class MissingClass does not exist -Test typeMap: {"array":"MongoDB\\BSON\\Unserializable"} +Test typeMap: {"fieldPaths":{"x":"MissingClass"}} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable +Class MissingClass does not exist -Test typeMap: {"document":"MissingClass"} +Test typeMap: {"array":"MyAbstractDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MissingClass does not exist +Class MyAbstractDocument is not instantiatable Test typeMap: {"document":"MyAbstractDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException Class MyAbstractDocument is not instantiatable -Test typeMap: {"document":"MyDocument"} +Test typeMap: {"root":"MyAbstractDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyDocument does not implement MongoDB\BSON\Unserializable +Class MyAbstractDocument is not instantiatable -Test typeMap: {"document":"MongoDB\\BSON\\Unserializable"} +Test typeMap: {"fieldPaths":{"x":"MyAbstractDocument"}} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable +Class MyAbstractDocument is not instantiatable -Test typeMap: {"root":"MissingClass"} +Test typeMap: {"array":"MyDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MissingClass does not exist +Class MyDocument does not implement MongoDB\BSON\Unserializable -Test typeMap: {"root":"MyAbstractDocument"} +Test typeMap: {"document":"MyDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyAbstractDocument is not instantiatable +Class MyDocument does not implement MongoDB\BSON\Unserializable Test typeMap: {"root":"MyDocument"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException Class MyDocument does not implement MongoDB\BSON\Unserializable +Test typeMap: {"fieldPaths":{"x":"MyDocument"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Class MyDocument does not implement MongoDB\BSON\Unserializable + +Test typeMap: {"array":"MongoDB\\BSON\\Unserializable"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Interface MongoDB\BSON\Unserializable is not instantiatable + +Test typeMap: {"document":"MongoDB\\BSON\\Unserializable"} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Interface MongoDB\BSON\Unserializable is not instantiatable + Test typeMap: {"root":"MongoDB\\BSON\\Unserializable"} OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable +Interface MongoDB\BSON\Unserializable is not instantiatable + +Test typeMap: {"fieldPaths":{"x":"MongoDB\\BSON\\Unserializable"}} +OK: Got MongoDB\Driver\Exception\InvalidArgumentException +Interface MongoDB\BSON\Unserializable is not instantiatable ===DONE=== diff --git a/tests/cursor/cursor-setTypeMap_error-003.phpt b/tests/cursor/cursor-setTypeMap_error-003.phpt index 492d6bd8d..f1420e63c 100644 --- a/tests/cursor/cursor-setTypeMap_error-003.phpt +++ b/tests/cursor/cursor-setTypeMap_error-003.phpt @@ -9,25 +9,15 @@ Cursor::setTypeMap(): fieldPaths must be an array, with single key/string elemen require_once __DIR__ . "/../utils/basic.inc"; -abstract class MyAbstractDocument implements MongoDB\BSON\Unserializable {} - -class MyDocument {} - -$fieldPaths = [ - 'notAnArray', - ['notAssociative'], - ['missing' => 'MissingClass'], - ['abstract' => 'MyAbstractDocument'], - ['my' => 'MyDocument'], - ['unserialize' => 'MongoDB\BSON\Unserializable'], +$typeMaps = [ + ['fieldPaths' => 'notAnArray'], + ['fieldPaths' => ['notAssociative']], ]; $manager = create_test_manager(); $cursor = $manager->executeQuery(NS, new MongoDB\Driver\Query([])); -foreach ($fieldPaths as $fieldPath) { - $typeMap = ['fieldPaths' => $fieldPath]; - +foreach ($typeMaps as $typeMap) { printf("Test typeMap: %s\n", json_encode($typeMap)); echo throws(function() use ($cursor, $typeMap) { @@ -47,20 +37,4 @@ Test typeMap: {"fieldPaths":["notAssociative"]} OK: Got MongoDB\Driver\Exception\InvalidArgumentException The 'fieldPaths' element is not an associative array -Test typeMap: {"fieldPaths":{"missing":"MissingClass"}} -OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MissingClass does not exist - -Test typeMap: {"fieldPaths":{"abstract":"MyAbstractDocument"}} -OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyAbstractDocument is not instantiatable - -Test typeMap: {"fieldPaths":{"my":"MyDocument"}} -OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MyDocument does not implement MongoDB\BSON\Unserializable - -Test typeMap: {"fieldPaths":{"unserialize":"MongoDB\\BSON\\Unserializable"}} -OK: Got MongoDB\Driver\Exception\InvalidArgumentException -Class MongoDB\BSON\Unserializable is not instantiatable - ===DONE===