Skip to content

Commit bc5d739

Browse files
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 - Remove the `UnitEnum` and `BackedEnum` interfaces from the list of interfaces implemented - List enum cases separately from constants, and show the underlying values, if any GH-15766
1 parent 8e8ba79 commit bc5d739

6 files changed

+161
-44
lines changed

ext/reflection/php_reflection.c

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ static void _const_string(smart_str *str, const char *name, zval *value, const c
306306
static void _function_string(smart_str *str, zend_function *fptr, zend_class_entry *scope, const char* indent);
307307
static void _property_string(smart_str *str, zend_property_info *prop, const char *prop_name, const char* indent);
308308
static void _class_const_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent);
309+
static void _enum_case_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char* indent);
309310
static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const char *indent);
310311
static void _extension_string(smart_str *str, const zend_module_entry *module, const char *indent);
311312
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
330331
kind = "Interface";
331332
} else if (ce->ce_flags & ZEND_ACC_TRAIT) {
332333
kind = "Trait";
334+
} else if (ce->ce_flags & ZEND_ACC_ENUM) {
335+
kind = "Enum";
333336
}
334337
smart_str_append_printf(str, "%s%s [ ", indent, kind);
335338
}
@@ -345,6 +348,8 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
345348
smart_str_append_printf(str, "interface ");
346349
} else if (ce->ce_flags & ZEND_ACC_TRAIT) {
347350
smart_str_append_printf(str, "trait ");
351+
} else if (ce->ce_flags & ZEND_ACC_ENUM) {
352+
smart_str_append_printf(str, "enum ");
348353
} else {
349354
if (ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) {
350355
smart_str_append_printf(str, "abstract ");
@@ -362,18 +367,47 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
362367
smart_str_append_printf(str, " extends %s", ZSTR_VAL(ce->parent->name));
363368
}
364369

365-
if (ce->num_interfaces) {
366-
uint32_t i;
370+
uint32_t num_interfaces = ce->num_interfaces;
371+
if (ce->ce_flags & ZEND_ACC_ENUM) {
372+
// Ignore `UnitEnum`
373+
num_interfaces--;
374+
if (ce->enum_backing_type != IS_UNDEF) {
375+
// Ignore `BackedEnum`
376+
num_interfaces--;
377+
// And show backing type
378+
if (ce->enum_backing_type == IS_STRING) {
379+
smart_str_append_printf(str, ": string");
380+
} else {
381+
smart_str_append_printf(str, ": int");
382+
}
383+
}
384+
}
385+
if (num_interfaces) {
386+
uint32_t next_interface = 0;
367387

368388
ZEND_ASSERT(ce->ce_flags & ZEND_ACC_LINKED);
389+
390+
#define IS_ENUM_INTERFACE(_iface) (_iface == zend_ce_unit_enum || _iface == zend_ce_backed_enum)
391+
if (ce->ce_flags & ZEND_ACC_ENUM && IS_ENUM_INTERFACE(ce->interfaces[0])) {
392+
next_interface++;
393+
if (ce->num_interfaces > 1 && IS_ENUM_INTERFACE(ce->interfaces[1])) {
394+
next_interface++;
395+
}
396+
}
397+
369398
if (ce->ce_flags & ZEND_ACC_INTERFACE) {
370399
smart_str_append_printf(str, " extends %s", ZSTR_VAL(ce->interfaces[0]->name));
371400
} else {
372-
smart_str_append_printf(str, " implements %s", ZSTR_VAL(ce->interfaces[0]->name));
401+
smart_str_append_printf(str, " implements %s", ZSTR_VAL(ce->interfaces[next_interface]->name));
373402
}
374-
for (i = 1; i < ce->num_interfaces; ++i) {
375-
smart_str_append_printf(str, ", %s", ZSTR_VAL(ce->interfaces[i]->name));
403+
next_interface++;
404+
// Going through the entire interface list, skipping the enum ones
405+
for (; next_interface < ce->num_interfaces; ++next_interface) {
406+
if (!IS_ENUM_INTERFACE(ce->interfaces[next_interface])) {
407+
smart_str_append_printf(str, ", %s", ZSTR_VAL(ce->interfaces[next_interface]->name));
408+
}
376409
}
410+
#undef IS_ENUM_INTERFACE
377411
}
378412
smart_str_append_printf(str, " ] {\n");
379413

@@ -384,23 +418,58 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, const
384418
}
385419

386420
/* Constants */
387-
smart_str_append_printf(str, "\n");
388-
count = zend_hash_num_elements(&ce->constants_table);
389-
smart_str_append_printf(str, "%s - Constants [%d] {\n", indent, count);
390-
if (count > 0) {
421+
int total_count = zend_hash_num_elements(&ce->constants_table);
422+
int constant_count = 0;
423+
int enum_case_count = 0;
424+
smart_str constant_str = {0};
425+
smart_str enum_case_str = {0};
426+
// So that we don't need to loop through all of the constants multiple
427+
// times (count the constants vs. enum cases, print the constants, print
428+
// the enum cases) use some temporary helper smart strings
429+
if (total_count > 0) {
391430
zend_string *key;
392431
zend_class_constant *c;
393432

394433
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, c) {
395-
_class_const_string(str, key, c, ZSTR_VAL(sub_indent));
434+
if (Z_TYPE(c->value) == IS_CONSTANT_AST
435+
&& zend_update_class_constant(c, key, c->ce) == FAILURE
436+
) {
437+
ZEND_ASSERT(EG(exception));
438+
zend_string_release(sub_indent);
439+
smart_str_free(&enum_case_str);
440+
smart_str_free(&constant_str);
441+
return;
442+
}
443+
if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE) {
444+
_enum_case_string(&enum_case_str, key, c, ZSTR_VAL(sub_indent));
445+
enum_case_count++;
446+
} else {
447+
_class_const_string(&constant_str, key, c, ZSTR_VAL(sub_indent));
448+
constant_count++;
449+
}
396450
if (UNEXPECTED(EG(exception))) {
397451
zend_string_release(sub_indent);
452+
smart_str_free(&enum_case_str);
453+
smart_str_free(&constant_str);
398454
return;
399455
}
400456
} ZEND_HASH_FOREACH_END();
401457
}
458+
// Enum cases go first, but the heading is only shown if there are any
459+
if (enum_case_count) {
460+
smart_str_append_printf(str, "\n");
461+
smart_str_append_printf(str, "%s - Enum cases [%d] {\n", indent, enum_case_count);
462+
smart_str_append_smart_str(str, &enum_case_str);
463+
smart_str_append_printf(str, "%s }\n", indent);
464+
}
465+
smart_str_append_printf(str, "\n");
466+
smart_str_append_printf(str, "%s - Constants [%d] {\n", indent, constant_count);
467+
smart_str_append_smart_str(str, &constant_str);
402468
smart_str_append_printf(str, "%s }\n", indent);
403469

470+
smart_str_free(&enum_case_str);
471+
smart_str_free(&constant_str);
472+
404473
/* Static properties */
405474
/* counting static properties */
406475
count = zend_hash_num_elements(&ce->properties_info);
@@ -626,6 +695,30 @@ static void _class_const_string(smart_str *str, const zend_string *name, zend_cl
626695
}
627696
/* }}} */
628697

698+
/* {{{ _enum_case_string */
699+
static void _enum_case_string(smart_str *str, const zend_string *name, zend_class_constant *c, const char *indent)
700+
{
701+
if (c->doc_comment) {
702+
smart_str_append_printf(str, "%s%s\n", indent, ZSTR_VAL(c->doc_comment));
703+
}
704+
smart_str_append_printf(str, "%sCase %s", indent, ZSTR_VAL(name));
705+
if (c->ce->enum_backing_type == IS_UNDEF) {
706+
// No value
707+
smart_str_appends(str, "\n");
708+
} else {
709+
// Has a value, which is the enum instance, get the value from that.
710+
// We know it must be either a string or integer so no need
711+
// for the IS_ARRAY or IS_OBJECT handling that _class_const_string()
712+
// requires
713+
zval *enum_val = zend_enum_fetch_case_value(Z_OBJ(c->value));
714+
zend_string *tmp_value_str;
715+
zend_string *value_str = zval_get_tmp_string(enum_val, &tmp_value_str);
716+
smart_str_append_printf(str, " = %s\n", ZSTR_VAL(value_str));
717+
zend_tmp_string_release(tmp_value_str);
718+
}
719+
}
720+
/* }}} */
721+
629722
static zend_op *get_recv_op(const zend_op_array *op_array, uint32_t offset)
630723
{
631724
zend_op *op = op_array->opcodes;

ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getEnum();
1111

1212
?>
1313
--EXPECTF--
14-
Class [ <user> final class Foo implements UnitEnum ] {
14+
Enum [ <user> enum Foo ] {
1515
@@ %sReflectionEnumUnitCase_getEnum.php 3-5
1616

17-
- Constants [1] {
18-
Constant [ public Foo Bar ] { Object }
17+
- Enum cases [1] {
18+
Case Bar
19+
}
20+
21+
- Constants [0] {
1922
}
2023

2124
- Static properties [0] {

ext/reflection/tests/ReflectionEnum_toString.phpt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ echo new ReflectionEnum(Foo::class);
1111

1212
?>
1313
--EXPECTF--
14-
Class [ <user> final class Foo implements UnitEnum ] {
14+
Enum [ <user> enum Foo ] {
1515
@@ %sReflectionEnum_toString.php 3-5
1616

17-
- Constants [1] {
18-
Constant [ public Foo Bar ] { Object }
17+
- Enum cases [1] {
18+
Case Bar
19+
}
20+
21+
- Constants [0] {
1922
}
2023

2124
- Static properties [0] {

ext/reflection/tests/ReflectionEnum_toString_backed_int.phpt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ var_export( MyBool::cases() );
2828

2929
?>
3030
--EXPECTF--
31-
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
31+
Enum [ <user> enum MyBool: int implements MyStringable ] {
3232
@@ %sReflectionEnum_toString_backed_int.php 7-16
3333

34-
- Constants [3] {
35-
Constant [ public MyBool MyFalse ] { Object }
36-
Constant [ public MyBool MyTrue ] { Object }
34+
- Enum cases [2] {
35+
Case MyFalse = 0
36+
Case MyTrue = 1
37+
}
38+
39+
- Constants [1] {
3740
Constant [ public MyBool OtherTrue ] { Object }
3841
}
3942

@@ -81,12 +84,15 @@ Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum
8184
}
8285
}
8386

84-
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
87+
Enum [ <user> enum MyBool: int implements MyStringable ] {
8588
@@ %sReflectionEnum_toString_backed_int.php 7-16
8689

87-
- Constants [3] {
88-
Constant [ public MyBool MyFalse ] { Object }
89-
Constant [ public MyBool MyTrue ] { Object }
90+
- Enum cases [2] {
91+
Case MyFalse = 0
92+
Case MyTrue = 1
93+
}
94+
95+
- Constants [1] {
9096
Constant [ public MyBool OtherTrue ] { Object }
9197
}
9298

ext/reflection/tests/ReflectionEnum_toString_backed_string.phpt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ var_export( MyBool::cases() );
2828

2929
?>
3030
--EXPECTF--
31-
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
31+
Enum [ <user> enum MyBool: string implements MyStringable ] {
3232
@@ %sReflectionEnum_toString_backed_string.php 7-16
3333

34-
- Constants [3] {
35-
Constant [ public MyBool MyFalse ] { Object }
36-
Constant [ public MyBool MyTrue ] { Object }
34+
- Enum cases [2] {
35+
Case MyFalse = ~FALSE~
36+
Case MyTrue = ~TRUE~
37+
}
38+
39+
- Constants [1] {
3740
Constant [ public MyBool OtherTrue ] { Object }
3841
}
3942

@@ -81,12 +84,15 @@ Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum
8184
}
8285
}
8386

84-
Class [ <user> final class MyBool implements MyStringable, UnitEnum, BackedEnum ] {
87+
Enum [ <user> enum MyBool: string implements MyStringable ] {
8588
@@ %sReflectionEnum_toString_backed_string.php 7-16
8689

87-
- Constants [3] {
88-
Constant [ public MyBool MyFalse ] { Object }
89-
Constant [ public MyBool MyTrue ] { Object }
90+
- Enum cases [2] {
91+
Case MyFalse = ~FALSE~
92+
Case MyTrue = ~TRUE~
93+
}
94+
95+
- Constants [1] {
9096
Constant [ public MyBool OtherTrue ] { Object }
9197
}
9298

ext/reflection/tests/ReflectionEnum_toString_unbacked.phpt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,17 @@ var_export( Suit::cases() );
3030

3131
?>
3232
--EXPECTF--
33-
Class [ <user> final class Suit implements MyStringable, UnitEnum ] {
33+
Enum [ <user> enum Suit implements MyStringable ] {
3434
@@ %sReflectionEnum_toString_unbacked.php 7-18
3535

36-
- Constants [5] {
37-
Constant [ public Suit Hearts ] { Object }
38-
Constant [ public Suit Diamonds ] { Object }
39-
Constant [ public Suit Clubs ] { Object }
40-
Constant [ public Suit Spades ] { Object }
36+
- Enum cases [4] {
37+
Case Hearts
38+
Case Diamonds
39+
Case Clubs
40+
Case Spades
41+
}
42+
43+
- Constants [1] {
4144
Constant [ public Suit OtherHearts ] { Object }
4245
}
4346

@@ -68,14 +71,17 @@ Class [ <user> final class Suit implements MyStringable, UnitEnum ] {
6871
}
6972
}
7073

71-
Class [ <user> final class Suit implements MyStringable, UnitEnum ] {
74+
Enum [ <user> enum Suit implements MyStringable ] {
7275
@@ %sReflectionEnum_toString_unbacked.php 7-18
7376

74-
- Constants [5] {
75-
Constant [ public Suit Hearts ] { Object }
76-
Constant [ public Suit Diamonds ] { Object }
77-
Constant [ public Suit Clubs ] { Object }
78-
Constant [ public Suit Spades ] { Object }
77+
- Enum cases [4] {
78+
Case Hearts
79+
Case Diamonds
80+
Case Clubs
81+
Case Spades
82+
}
83+
84+
- Constants [1] {
7985
Constant [ public Suit OtherHearts ] { Object }
8086
}
8187

0 commit comments

Comments
 (0)