Skip to content

PHPC-2083: BSON handling for enum classes #1378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
2 changes: 1 addition & 1 deletion config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
1 change: 0 additions & 1 deletion php_phongo.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion src/BSON/Persistable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
49 changes: 0 additions & 49 deletions src/BSON/PersistableEnum.c

This file was deleted.

15 changes: 0 additions & 15 deletions src/BSON/PersistableEnum.stub.php

This file was deleted.

31 changes: 0 additions & 31 deletions src/BSON/PersistableEnum_arginfo.h

This file was deleted.

15 changes: 14 additions & 1 deletion src/BSON/Unserializable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
160 changes: 24 additions & 136 deletions src/phongo_bson.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#include "bson/bson.h"

#include <php.h>
#if PHP_VERSION_ID >= 80100
#include <Zend/zend_enum.h>
#endif
#include <Zend/zend_interfaces.h>

#include "php_array_api.h"
Expand All @@ -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
Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}
Expand Down
Loading