1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 08:12:21 +01:00

ReflectionClass: show enums differently from classes

While internally enums are mostly the same as classes, their output in
`ReflectionClass::__toString()` should show the enum as the developer wrote it,
rather than as the engine stored it. Accordingly

- Say that the enum is an enum, not a final class

- Include the backing type, if any, in the declaration line

- List enum cases separately from constants, and show the underlying values, if
any

GH-15766
This commit is contained in:
Daniel Scherzer
2025-03-26 13:00:03 -07:00
committed by DanielEScherzer
parent 6c81f708c5
commit 4233394e8f
6 changed files with 126 additions and 39 deletions

View File

@@ -306,6 +306,7 @@ static void _const_string(smart_str *str, const char *name, zval *value, const c
static void _function_string(smart_str *str, zend_function *fptr, zend_class_entry *scope, const char* indent);
static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, const char* indent);
static void _class_const_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent);
static void _enum_case_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent);
static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const char *indent);
static void _extension_string(smart_str *str, const zend_module_entry *module, const char *indent);
static void _zend_extension_string(smart_str *str, const zend_extension *extension, const char *indent);
@@ -330,6 +331,8 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
kind = "Interface";
} else if (ce->ce_flags & ZEND_ACC_TRAIT) {
kind = "Trait";
} else if (ce->ce_flags & ZEND_ACC_ENUM) {
kind = "Enum";
}
smart_str_append_printf(str, "%s%s [ ", indent, kind);
}
@@ -345,6 +348,8 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
smart_str_append_printf(str, "interface ");
} else if (ce->ce_flags & ZEND_ACC_TRAIT) {
smart_str_append_printf(str, "trait ");
} else if (ce->ce_flags & ZEND_ACC_ENUM) {
smart_str_append_printf(str, "enum ");
} else {
if (ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) {
smart_str_append_printf(str, "abstract ");
@@ -362,6 +367,12 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
smart_str_append_printf(str, " extends %s", ZSTR_VAL(ce->parent->name));
}
// Show backing type of enums
if ((ce->ce_flags & ZEND_ACC_ENUM) && (ce->enum_backing_type != IS_UNDEF)) {
smart_str_append_printf(str,
ce->enum_backing_type == IS_STRING ? ": string" : ": int"
);
}
if (ce->num_interfaces) {
uint32_t i;
@@ -384,23 +395,49 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
}
/* Constants */
smart_str_append_printf(str, "\n");
count = zend_hash_num_elements(&ce->constants_table);
smart_str_append_printf(str, "%s - Constants [%d] {\n", indent, count);
if (count > 0) {
uint32_t total_count = zend_hash_num_elements(&ce->constants_table);
uint32_t constant_count = 0;
uint32_t enum_case_count = 0;
smart_str constant_str = {0};
smart_str enum_case_str = {0};
/* So that we don't need to loop through all of the constants multiple
* times (count the constants vs. enum cases, print the constants, print
* the enum cases) use some temporary helper smart strings. */
if (total_count > 0) {
zend_string *key;
zend_class_constant *c;
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, c) {
_class_const_string(str, key, c, ZSTR_VAL(sub_indent));
if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE) {
_enum_case_string(&enum_case_str, key, c, ZSTR_VAL(sub_indent));
enum_case_count++;
} else {
_class_const_string(&constant_str, key, c, ZSTR_VAL(sub_indent));
constant_count++;
}
if (UNEXPECTED(EG(exception))) {
zend_string_release(sub_indent);
smart_str_free(&enum_case_str);
smart_str_free(&constant_str);
return;
}
} ZEND_HASH_FOREACH_END();
}
// Enum cases go first, but the heading is only shown if there are any
if (enum_case_count) {
smart_str_appendc(str, '\n');
smart_str_append_printf(str, "%s - Enum cases [%d] {\n", indent, enum_case_count);
smart_str_append_smart_str(str, &enum_case_str);
smart_str_append_printf(str, "%s }\n", indent);
}
smart_str_appendc(str, '\n');
smart_str_append_printf(str, "%s - Constants [%d] {\n", indent, constant_count);
smart_str_append_smart_str(str, &constant_str);
smart_str_append_printf(str, "%s }\n", indent);
smart_str_free(&enum_case_str);
smart_str_free(&constant_str);
/* Static properties */
/* counting static properties */
count = zend_hash_num_elements(&ce->properties_info);
@@ -626,6 +663,32 @@ static void _class_const_string(smart_str *str, const zend_string *name, zend_cl
}
/* }}} */
static void _enum_case_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char *indent)
{
if (Z_TYPE(c->value) == IS_CONSTANT_AST && zend_update_class_constant(c, name, c->ce) == FAILURE) {
return;
}
if (c->doc_comment) {
smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(c->doc_comment));
}
smart_str_append_printf(str, "%sCase %s", indent, ZSTR_VAL(name));
if (c->ce->enum_backing_type == IS_UNDEF) {
// No value
smart_str_appendc(str, '\n');
} else {
/* Has a value, which is the enum instance, get the value from that.
* We know it must be either a string or integer so no need
* for the IS_ARRAY or IS_OBJECT handling that _class_const_string()
* requires. */
zval *enum_val = zend_enum_fetch_case_value(Z_OBJ(c->value));
zend_string *tmp_value_str;
zend_string *value_str = zval_get_tmp_string(enum_val, &tmp_value_str);
smart_str_append_printf(str, " = %s\n", ZSTR_VAL(value_str));
zend_tmp_string_release(tmp_value_str);
}
}
static zend_op *get_recv_op(const zend_op_array *op_array, uint32_t offset)
{
zend_op *op = op_array->opcodes;

View File

@@ -11,11 +11,14 @@ echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getEnum();
?>
--EXPECTF--
Class [ <user> final class Foo implements UnitEnum ] {
Enum [ <user> enum Foo implements UnitEnum ] {
@@ %sReflectionEnumUnitCase_getEnum.php 3-5
- Constants [1] {
Constant [ public Foo Bar ] { Object }
- Enum cases [1] {
Case Bar
}
- Constants [0] {
}
- Static properties [0] {

View File

@@ -11,11 +11,14 @@ echo new ReflectionEnum(Foo::class);
?>
--EXPECTF--
Class [ <user> final class Foo implements UnitEnum ] {
Enum [ <user> enum Foo implements UnitEnum ] {
@@ %sReflectionEnum_toString.php 3-5
- Constants [1] {
Constant [ public Foo Bar ] { Object }
- Enum cases [1] {
Case Bar
}
- Constants [0] {
}
- Static properties [0] {

View File

@@ -28,12 +28,15 @@ var_export( MyBool::cases() );
?>
--EXPECTF--
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
Enum [ <user> enum MyBool: int implements MyStringable, UnitEnum, BackedEnum ] {
@@ %sReflectionEnum_toString_backed_int.php 7-16
- Constants [3] {
Constant [ public MyBool MyFalse ] { Object }
Constant [ public MyBool MyTrue ] { Object }
- Enum cases [2] {
Case MyFalse = 0
Case MyTrue = 1
}
- Constants [1] {
Constant [ public MyBool OtherTrue ] { Object }
}
@@ -81,12 +84,15 @@ Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum
}
}
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
Enum [ <user> enum MyBool: int implements MyStringable, UnitEnum, BackedEnum ] {
@@ %sReflectionEnum_toString_backed_int.php 7-16
- Constants [3] {
Constant [ public MyBool MyFalse ] { Object }
Constant [ public MyBool MyTrue ] { Object }
- Enum cases [2] {
Case MyFalse = 0
Case MyTrue = 1
}
- Constants [1] {
Constant [ public MyBool OtherTrue ] { Object }
}

View File

@@ -28,12 +28,15 @@ var_export( MyBool::cases() );
?>
--EXPECTF--
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
Enum [ <user> enum MyBool: string implements MyStringable, UnitEnum, BackedEnum ] {
@@ %sReflectionEnum_toString_backed_string.php 7-16
- Constants [3] {
Constant [ public MyBool MyFalse ] { Object }
Constant [ public MyBool MyTrue ] { Object }
- Enum cases [2] {
Case MyFalse = ~FALSE~
Case MyTrue = ~TRUE~
}
- Constants [1] {
Constant [ public MyBool OtherTrue ] { Object }
}
@@ -81,12 +84,15 @@ Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum
}
}
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
Enum [ <user> enum MyBool: string implements MyStringable, UnitEnum, BackedEnum ] {
@@ %sReflectionEnum_toString_backed_string.php 7-16
- Constants [3] {
Constant [ public MyBool MyFalse ] { Object }
Constant [ public MyBool MyTrue ] { Object }
- Enum cases [2] {
Case MyFalse = ~FALSE~
Case MyTrue = ~TRUE~
}
- Constants [1] {
Constant [ public MyBool OtherTrue ] { Object }
}

View File

@@ -30,14 +30,17 @@ var_export( Suit::cases() );
?>
--EXPECTF--
Class [ <user> final class Suit implements MyStringable, UnitEnum ] {
Enum [ <user> enum Suit implements MyStringable, UnitEnum ] {
@@ %sReflectionEnum_toString_unbacked.php 7-18
- Constants [5] {
Constant [ public Suit Hearts ] { Object }
Constant [ public Suit Diamonds ] { Object }
Constant [ public Suit Clubs ] { Object }
Constant [ public Suit Spades ] { Object }
- Enum cases [4] {
Case Hearts
Case Diamonds
Case Clubs
Case Spades
}
- Constants [1] {
Constant [ public Suit OtherHearts ] { Object }
}
@@ -68,14 +71,17 @@ Class [ <user> final class Suit implements MyStringable, UnitEnum ] {
}
}
Class [ <user> final class Suit implements MyStringable, UnitEnum ] {
Enum [ <user> enum Suit implements MyStringable, UnitEnum ] {
@@ %sReflectionEnum_toString_unbacked.php 7-18
- Constants [5] {
Constant [ public Suit Hearts ] { Object }
Constant [ public Suit Diamonds ] { Object }
Constant [ public Suit Clubs ] { Object }
Constant [ public Suit Spades ] { Object }
- Enum cases [4] {
Case Hearts
Case Diamonds
Case Clubs
Case Spades
}
- Constants [1] {
Constant [ public Suit OtherHearts ] { Object }
}