diff --git a/NEWS b/NEWS index fdbf85be8b4..976713cefdb 100644 --- a/NEWS +++ b/NEWS @@ -44,6 +44,9 @@ PHP NEWS . Fixed bug GH-15711 (SoapClient can't convert BackedEnum to scalar value). (nielsdos) +- Standard: + . Add support for backed enums in http_build_query(). (ilutov) + 12 Sep 2024, PHP 8.4.0beta5 - BCMath: diff --git a/UPGRADING b/UPGRADING index ea6f9e79c75..d2965d33966 100644 --- a/UPGRADING +++ b/UPGRADING @@ -225,6 +225,7 @@ PHP 8.4 UPGRADE NOTES . php_uname() now throws ValueErrors on invalid inputs. . The "allowed_classes" option for unserialize() now throws TypeErrors and ValueErrors if it is not an array of class names. + . http_build_query() now correctly handles backed enums. - Tidy: . Failures in the constructor now throw exceptions rather than emitting diff --git a/ext/standard/http.c b/ext/standard/http.c index 5f796a4a402..fd862b605a7 100644 --- a/ext/standard/http.c +++ b/ext/standard/http.c @@ -20,6 +20,7 @@ #include "SAPI.h" #include "zend_exceptions.h" #include "basic_functions.h" +#include "zend_enum.h" static void php_url_encode_scalar(zval *scalar, smart_str *form_str, int encoding_type, zend_ulong index_int, @@ -56,6 +57,7 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str, } smart_str_appendc(form_str, '='); +try_again: switch (Z_TYPE_P(scalar)) { case IS_STRING: { zend_string *encoded_data; @@ -90,6 +92,14 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str, case IS_TRUE: smart_str_appendc(form_str, '1'); break; + case IS_OBJECT: + ZEND_ASSERT(Z_OBJCE_P(scalar)->ce_flags & ZEND_ACC_ENUM); + if (Z_OBJCE_P(scalar)->enum_backing_type == IS_UNDEF) { + zend_value_error("Unbacked enum %s cannot be converted to a string", ZSTR_VAL(Z_OBJCE_P(scalar)->name)); + return; + } + scalar = zend_enum_fetch_case_value(Z_OBJ_P(scalar)); + goto try_again; /* All possible types are either handled here or previously */ EMPTY_SWITCH_DEFAULT_CASE(); } @@ -154,7 +164,9 @@ PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr, } ZVAL_DEREF(zdata); - if (Z_TYPE_P(zdata) == IS_ARRAY || Z_TYPE_P(zdata) == IS_OBJECT) { + if (Z_TYPE_P(zdata) == IS_ARRAY + || (Z_TYPE_P(zdata) == IS_OBJECT + && !(Z_OBJCE_P(zdata)->ce_flags & ZEND_ACC_ENUM))) { zend_string *new_prefix; if (key) { zend_string *encoded_key; @@ -233,6 +245,11 @@ PHP_FUNCTION(http_build_query) Z_PARAM_LONG(enc_type) ZEND_PARSE_PARAMETERS_END(); + if (UNEXPECTED(Z_TYPE_P(formdata) == IS_OBJECT && (Z_OBJCE_P(formdata)->ce_flags & ZEND_ACC_ENUM))) { + zend_argument_type_error(1, "must be of type array, %s given", zend_zval_value_name(formdata)); + RETURN_THROWS(); + } + php_url_encode_hash_ex(HASH_OF(formdata), &formstr, prefix, prefix_len, /* key_prefix */ NULL, (Z_TYPE_P(formdata) == IS_OBJECT ? formdata : NULL), arg_sep, (int)enc_type); RETURN_STR(smart_str_extract(&formstr)); diff --git a/ext/standard/tests/http/gh15650.phpt b/ext/standard/tests/http/gh15650.phpt new file mode 100644 index 00000000000..239e0c85164 --- /dev/null +++ b/ext/standard/tests/http/gh15650.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-15650: http_build_query() with enum +--FILE-- + E1::C, 'e2' => E2::C]), "\n"; + +try { + echo http_build_query(['e3' => E3::C]); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + echo http_build_query(E1::C); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +e1=hello+world%21&e2=42 +ValueError: Unbacked enum E3 cannot be converted to a string +TypeError: http_build_query(): Argument #1 ($data) must be of type array, E1 given