Skip to content

Commit 594cad9

Browse files
committed
PHPC-2083: Revise BSON handling of enum classes
Revert previous enum instantiation behavior and PersistableEnum trait from de5f1e5 Backed enums will be encoded as their case value. Non-backed cannot be encoded and no enums can be encoded at the root level. Prohibit enums from implementing Persistable and Unserializable.
1 parent 7423da4 commit 594cad9

21 files changed

+250
-702
lines changed

config.m4

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ if test "$PHP_MONGODB" != "no"; then
125125
src/BSON/ObjectId.c \
126126
src/BSON/ObjectIdInterface.c \
127127
src/BSON/Persistable.c \
128-
src/BSON/PersistableEnum.c \
129128
src/BSON/Regex.c \
130129
src/BSON/RegexInterface.c \
131130
src/BSON/Serializable.c \

config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ if (PHP_MONGODB != "no") {
121121

122122
EXTENSION("mongodb", "php_phongo.c", null, PHP_MONGODB_CFLAGS);
123123
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");
124-
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");
124+
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");
125125
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");
126126
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");
127127
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");

php_phongo.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ PHP_MINIT_FUNCTION(mongodb) /* {{{ */
216216
php_phongo_minkey_init_ce(INIT_FUNC_ARGS_PASSTHRU);
217217
php_phongo_objectid_init_ce(INIT_FUNC_ARGS_PASSTHRU);
218218
php_phongo_persistable_init_ce(INIT_FUNC_ARGS_PASSTHRU);
219-
php_phongo_persistableenum_init_ce(INIT_FUNC_ARGS_PASSTHRU);
220219
php_phongo_regex_init_ce(INIT_FUNC_ARGS_PASSTHRU);
221220
php_phongo_symbol_init_ce(INIT_FUNC_ARGS_PASSTHRU);
222221
php_phongo_timestamp_init_ce(INIT_FUNC_ARGS_PASSTHRU);

src/BSON/Persistable.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,20 @@
2121

2222
zend_class_entry* php_phongo_persistable_ce;
2323

24+
static int php_phongo_implement_persistable(zend_class_entry* interface, zend_class_entry* class_type)
25+
{
26+
#if PHP_VERSION_ID >= 80100
27+
if (class_type->ce_flags & ZEND_ACC_ENUM) {
28+
zend_error_noreturn(E_ERROR, "Enum class %s cannot implement interface %s", ZSTR_VAL(class_type->name), ZSTR_VAL(interface->name));
29+
return FAILURE;
30+
}
31+
#endif /* PHP_VERSION_ID >= 80100 */
32+
33+
return SUCCESS;
34+
}
35+
2436
void php_phongo_persistable_init_ce(INIT_FUNC_ARGS)
2537
{
26-
php_phongo_persistable_ce = register_class_MongoDB_BSON_Persistable(php_phongo_serializable_ce, php_phongo_unserializable_ce);
38+
php_phongo_persistable_ce = register_class_MongoDB_BSON_Persistable(php_phongo_serializable_ce, php_phongo_unserializable_ce);
39+
php_phongo_persistable_ce->interface_gets_implemented = php_phongo_implement_persistable;
2740
}

src/BSON/PersistableEnum.c

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/BSON/PersistableEnum.stub.php

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/BSON/PersistableEnum_arginfo.h

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/BSON/Unserializable.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,20 @@
2121

2222
zend_class_entry* php_phongo_unserializable_ce;
2323

24+
static int php_phongo_implement_unserializable(zend_class_entry* interface, zend_class_entry* class_type)
25+
{
26+
#if PHP_VERSION_ID >= 80100
27+
if (class_type->ce_flags & ZEND_ACC_ENUM) {
28+
zend_error_noreturn(E_ERROR, "Enum class %s cannot implement interface %s", ZSTR_VAL(class_type->name), ZSTR_VAL(interface->name));
29+
return FAILURE;
30+
}
31+
#endif /* PHP_VERSION_ID >= 80100 */
32+
33+
return SUCCESS;
34+
}
35+
2436
void php_phongo_unserializable_init_ce(INIT_FUNC_ARGS)
2537
{
26-
php_phongo_unserializable_ce = register_class_MongoDB_BSON_Unserializable();
38+
php_phongo_unserializable_ce = register_class_MongoDB_BSON_Unserializable();
39+
php_phongo_unserializable_ce->interface_gets_implemented = php_phongo_implement_unserializable;
2740
}

src/phongo_bson.c

Lines changed: 2 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -783,95 +783,6 @@ static void php_phongo_handle_field_path_entry_for_compound_type(php_phongo_bson
783783
}
784784
}
785785

786-
#if PHP_VERSION_ID >= 80100
787-
/* Resolves an enum class and case name to a zval. On error, an exception will
788-
* have been thrown and NULL will be returned.
789-
*
790-
* This function is modeled after php_var_unserialize_internal in php-src. */
791-
static zval* resolve_enum_case(zend_class_entry* ce, const char* case_name)
792-
{
793-
zval* return_value = NULL;
794-
zend_string* c_str = NULL;
795-
zend_class_constant* c;
796-
797-
if (!(ce->ce_flags & ZEND_ACC_ENUM)) {
798-
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Class '%s' is not an enum", ZSTR_VAL(ce->name));
799-
goto cleanup;
800-
}
801-
802-
c_str = zend_string_init(case_name, strlen(case_name), 0);
803-
c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), c_str);
804-
805-
if (!c) {
806-
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Undefined constant %s::%s", ZSTR_VAL(ce->name), case_name);
807-
goto cleanup;
808-
}
809-
810-
if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
811-
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "%s::%s is not an enum case", ZSTR_VAL(ce->name), case_name);
812-
goto cleanup;
813-
}
814-
815-
if (Z_TYPE(c->value) == IS_CONSTANT_AST && zval_update_constant_ex(&c->value, ce) == FAILURE) {
816-
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Failed to evaluate constant expression AST for %s::%s", ZSTR_VAL(ce->name), case_name);
817-
goto cleanup;
818-
}
819-
820-
if (Z_TYPE(c->value) != IS_OBJECT) {
821-
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));
822-
}
823-
824-
return_value = &c->value;
825-
826-
cleanup:
827-
if (c_str) {
828-
zend_string_release_ex(c_str, 0);
829-
}
830-
831-
return return_value;
832-
}
833-
#endif /* PHP_VERSION_ID >= 80100 */
834-
835-
static bool php_phongo_bson_init_document_object(zval* src, zend_class_entry* obj_ce, zval* obj)
836-
{
837-
#if PHP_VERSION_ID >= 80100
838-
/* Enums require special handling for instantiation */
839-
if (obj_ce->ce_flags & ZEND_ACC_ENUM) {
840-
int plen;
841-
zend_bool pfree;
842-
char* case_name;
843-
zval* enum_case;
844-
845-
case_name = php_array_fetchc_string(src, "name", &plen, &pfree);
846-
847-
if (!case_name) {
848-
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Missing 'name' field to infer enum case for %s", ZSTR_VAL(obj_ce->name));
849-
850-
return false;
851-
}
852-
853-
enum_case = resolve_enum_case(obj_ce, case_name);
854-
855-
if (pfree) {
856-
efree(case_name);
857-
}
858-
859-
if (!enum_case) {
860-
/* Exception already thrown */
861-
return false;
862-
}
863-
864-
ZVAL_COPY(obj, enum_case);
865-
} else {
866-
object_init_ex(obj, obj_ce);
867-
}
868-
#else /* PHP_VERSION_ID < 80100 */
869-
object_init_ex(obj, obj_ce);
870-
#endif /* PHP_VERSION_ID */
871-
872-
return true;
873-
}
874-
875786
static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, const char* key, const bson_t* v_document, void* data)
876787
{
877788
zval* retval = PHONGO_BSON_STATE_ZCHILD(data);
@@ -918,18 +829,12 @@ static bool php_phongo_bson_visit_document(const bson_iter_t* iter ARG_UNUSED, c
918829
zval obj;
919830
zend_class_entry* obj_ce = state.odm ? state.odm : state.map.document.ce;
920831

921-
if (!php_phongo_bson_init_document_object(&state.zchild, obj_ce, &obj)) {
922-
/* Exception already thrown. Clean up and return
923-
* true to stop iteration for our parent context. */
924-
zval_ptr_dtor(&state.zchild);
925-
php_phongo_bson_state_dtor(&state);
926-
return true;
927-
}
832+
object_init_ex(&obj, obj_ce);
928833

929834
zend_call_method_with_1_params(PHONGO_COMPAT_OBJ_P(&obj), NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, &state.zchild);
930-
931835
zval_ptr_dtor(&state.zchild);
932836
ZVAL_COPY_VALUE(&state.zchild, &obj);
837+
933838
break;
934839
}
935840

@@ -1142,40 +1047,7 @@ bool php_phongo_bson_to_zval_ex(const bson_t* b, php_phongo_bson_state* state)
11421047
zval obj;
11431048
zend_class_entry* obj_ce = state->odm ? state->odm : state->map.root.ce;
11441049

1145-
#if PHP_VERSION_ID >= 80100
1146-
/* Enums require special handling for instantiation */
1147-
if (obj_ce->ce_flags & ZEND_ACC_ENUM) {
1148-
int plen;
1149-
zend_bool pfree;
1150-
char* case_name;
1151-
zval* enum_case;
1152-
1153-
case_name = php_array_fetchc_string(&state->zchild, "name", &plen, &pfree);
1154-
1155-
if (!case_name) {
1156-
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Missing 'name' field to infer enum case for %s", ZSTR_VAL(obj_ce->name));
1157-
1158-
goto cleanup;
1159-
}
1160-
1161-
enum_case = resolve_enum_case(obj_ce, case_name);
1162-
1163-
if (pfree) {
1164-
efree(case_name);
1165-
}
1166-
1167-
if (!enum_case) {
1168-
/* Exception already thrown */
1169-
goto cleanup;
1170-
}
1171-
1172-
ZVAL_COPY(&obj, enum_case);
1173-
} else {
1174-
object_init_ex(&obj, obj_ce);
1175-
}
1176-
#else /* PHP_VERSION_ID < 80100 */
11771050
object_init_ex(&obj, obj_ce);
1178-
#endif /* PHP_VERSION_ID */
11791051

11801052
zend_call_method_with_1_params(PHONGO_COMPAT_OBJ_P(&obj), NULL, NULL, BSON_UNSERIALIZE_FUNC_NAME, NULL, &state->zchild);
11811053
zval_ptr_dtor(&state->zchild);

src/phongo_bson_encode.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#include "bson/bson.h"
1818

1919
#include <php.h>
20+
#if PHP_VERSION_ID >= 80100
21+
#include <Zend/zend_enum.h>
22+
#endif
2023
#include <Zend/zend_interfaces.h>
2124

2225
#include "php_phongo.h"
@@ -43,6 +46,7 @@
4346
#endif
4447

4548
/* Forwards declarations */
49+
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);
4650
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);
4751

4852
/* 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
236240

237241
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));
238242
return;
239-
} else {
243+
}
244+
245+
#if PHP_VERSION_ID >= 80100
246+
if (Z_TYPE_P(object) == IS_OBJECT && Z_OBJCE_P(object)->ce_flags & ZEND_ACC_ENUM) {
247+
if (Z_OBJCE_P(object)->enum_backing_type == IS_UNDEF) {
248+
char* path_string = php_phongo_field_path_as_string(field_path);
249+
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);
250+
efree(path_string);
251+
return;
252+
}
253+
254+
php_phongo_bson_append(bson, field_path, flags, key, key_len, zend_enum_fetch_case_value(Z_OBJ_P(object)));
255+
return;
256+
}
257+
#endif /* PHP_VERSION_ID >= 80100 */
258+
259+
{
240260
bson_t child;
241261

242262
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*
389409
break;
390410
}
391411

412+
/* For the error handling that follows, we can safely assume that we
413+
* are at the root level, since php_phongo_bson_append_object would
414+
* have already been called for a non-root level. */
415+
#if PHP_VERSION_ID >= 80100
416+
if (Z_OBJCE_P(data)->ce_flags & ZEND_ACC_ENUM) {
417+
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Enum %s cannot be serialized as a root element", ZSTR_VAL(Z_OBJCE_P(data)->name));
418+
return;
419+
}
420+
#endif /* PHP_VERSION_ID >= 80100 */
421+
392422
if (instanceof_function(Z_OBJCE_P(data), php_phongo_type_ce)) {
393423
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));
394424
return;

0 commit comments

Comments
 (0)