diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bc8ffbdd8bd8e..7aedca43d02ce 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -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; diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt index f9c05d2f6e596..c3d925913a481 100644 --- a/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt +++ b/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt @@ -11,11 +11,14 @@ echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getEnum(); ?> --EXPECTF-- -Class [ final class Foo implements UnitEnum ] { +Enum [ 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] { diff --git a/ext/reflection/tests/ReflectionEnum_toString.phpt b/ext/reflection/tests/ReflectionEnum_toString.phpt index 91ef587a9a3a4..5407afba682ae 100644 --- a/ext/reflection/tests/ReflectionEnum_toString.phpt +++ b/ext/reflection/tests/ReflectionEnum_toString.phpt @@ -11,11 +11,14 @@ echo new ReflectionEnum(Foo::class); ?> --EXPECTF-- -Class [ final class Foo implements UnitEnum ] { +Enum [ 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] { diff --git a/ext/reflection/tests/ReflectionEnum_toString_backed_int.phpt b/ext/reflection/tests/ReflectionEnum_toString_backed_int.phpt new file mode 100644 index 0000000000000..24ffbf918eaaa --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_toString_backed_int.phpt @@ -0,0 +1,147 @@ +--TEST-- +ReflectionEnum::__toString() (larger case, int-backed) +--FILE-- +name . " = " . $this->value; + } +} + +$r = new ReflectionClass( MyBool::class ); +echo $r; +echo "\n"; +$r = new ReflectionEnum( MyBool::class ); +echo $r; + +var_export( MyBool::cases() ); + +?> +--EXPECTF-- +Enum [ enum MyBool: int implements MyStringable, UnitEnum, BackedEnum ] { + @@ %sReflectionEnum_toString_backed_int.php 7-16 + + - Enum cases [2] { + Case MyFalse = 0 + Case MyTrue = 1 + } + + - Constants [1] { + Constant [ public MyBool OtherTrue ] { Object } + } + + - Static properties [0] { + } + + - Static methods [3] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + + Method [ static public method from ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ static ] + } + + Method [ static public method tryFrom ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ ?static ] + } + } + + - Properties [2] { + Property [ public protected(set) readonly string $name ] + Property [ public protected(set) readonly int $value ] + } + + - Methods [1] { + Method [ public method toString ] { + @@ %sReflectionEnum_toString_backed_int.php 13 - 15 + + - Parameters [0] { + } + - Return [ string ] + } + } +} + +Enum [ enum MyBool: int implements MyStringable, UnitEnum, BackedEnum ] { + @@ %sReflectionEnum_toString_backed_int.php 7-16 + + - Enum cases [2] { + Case MyFalse = 0 + Case MyTrue = 1 + } + + - Constants [1] { + Constant [ public MyBool OtherTrue ] { Object } + } + + - Static properties [0] { + } + + - Static methods [3] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + + Method [ static public method from ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ static ] + } + + Method [ static public method tryFrom ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ ?static ] + } + } + + - Properties [2] { + Property [ public protected(set) readonly string $name ] + Property [ public protected(set) readonly int $value ] + } + + - Methods [1] { + Method [ public method toString ] { + @@ %sReflectionEnum_toString_backed_int.php 13 - 15 + + - Parameters [0] { + } + - Return [ string ] + } + } +} +array ( + 0 => + \MyBool::MyFalse, + 1 => + \MyBool::MyTrue, +) diff --git a/ext/reflection/tests/ReflectionEnum_toString_backed_string.phpt b/ext/reflection/tests/ReflectionEnum_toString_backed_string.phpt new file mode 100644 index 0000000000000..4c38d2b624b39 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_toString_backed_string.phpt @@ -0,0 +1,147 @@ +--TEST-- +ReflectionEnum::__toString() (larger case, string-backed) +--FILE-- +name . " = " . $this->value; + } +} + +$r = new ReflectionClass( MyBool::class ); +echo $r; +echo "\n"; +$r = new ReflectionEnum( MyBool::class ); +echo $r; + +var_export( MyBool::cases() ); + +?> +--EXPECTF-- +Enum [ enum MyBool: string implements MyStringable, UnitEnum, BackedEnum ] { + @@ %sReflectionEnum_toString_backed_string.php 7-16 + + - Enum cases [2] { + Case MyFalse = ~FALSE~ + Case MyTrue = ~TRUE~ + } + + - Constants [1] { + Constant [ public MyBool OtherTrue ] { Object } + } + + - Static properties [0] { + } + + - Static methods [3] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + + Method [ static public method from ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ static ] + } + + Method [ static public method tryFrom ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ ?static ] + } + } + + - Properties [2] { + Property [ public protected(set) readonly string $name ] + Property [ public protected(set) readonly string $value ] + } + + - Methods [1] { + Method [ public method toString ] { + @@ %sReflectionEnum_toString_backed_string.php 13 - 15 + + - Parameters [0] { + } + - Return [ string ] + } + } +} + +Enum [ enum MyBool: string implements MyStringable, UnitEnum, BackedEnum ] { + @@ %sReflectionEnum_toString_backed_string.php 7-16 + + - Enum cases [2] { + Case MyFalse = ~FALSE~ + Case MyTrue = ~TRUE~ + } + + - Constants [1] { + Constant [ public MyBool OtherTrue ] { Object } + } + + - Static properties [0] { + } + + - Static methods [3] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + + Method [ static public method from ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ static ] + } + + Method [ static public method tryFrom ] { + + - Parameters [1] { + Parameter #0 [ string|int $value ] + } + - Return [ ?static ] + } + } + + - Properties [2] { + Property [ public protected(set) readonly string $name ] + Property [ public protected(set) readonly string $value ] + } + + - Methods [1] { + Method [ public method toString ] { + @@ %sReflectionEnum_toString_backed_string.php 13 - 15 + + - Parameters [0] { + } + - Return [ string ] + } + } +} +array ( + 0 => + \MyBool::MyFalse, + 1 => + \MyBool::MyTrue, +) diff --git a/ext/reflection/tests/ReflectionEnum_toString_unbacked.phpt b/ext/reflection/tests/ReflectionEnum_toString_unbacked.phpt new file mode 100644 index 0000000000000..ec3d73ab70452 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_toString_unbacked.phpt @@ -0,0 +1,123 @@ +--TEST-- +ReflectionEnum::__toString() (larger case, unbacked) +--FILE-- +name; + } +} + +$r = new ReflectionClass( Suit::class ); +echo $r; +echo "\n"; +$r = new ReflectionEnum( Suit::class ); +echo $r; + +var_export( Suit::cases() ); + +?> +--EXPECTF-- +Enum [ enum Suit implements MyStringable, UnitEnum ] { + @@ %sReflectionEnum_toString_unbacked.php 7-18 + + - Enum cases [4] { + Case Hearts + Case Diamonds + Case Clubs + Case Spades + } + + - Constants [1] { + Constant [ public Suit OtherHearts ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public protected(set) readonly string $name ] + } + + - Methods [1] { + Method [ public method toString ] { + @@ %sReflectionEnum_toString_unbacked.php 15 - 17 + + - Parameters [0] { + } + - Return [ string ] + } + } +} + +Enum [ enum Suit implements MyStringable, UnitEnum ] { + @@ %sReflectionEnum_toString_unbacked.php 7-18 + + - Enum cases [4] { + Case Hearts + Case Diamonds + Case Clubs + Case Spades + } + + - Constants [1] { + Constant [ public Suit OtherHearts ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public protected(set) readonly string $name ] + } + + - Methods [1] { + Method [ public method toString ] { + @@ %sReflectionEnum_toString_unbacked.php 15 - 17 + + - Parameters [0] { + } + - Return [ string ] + } + } +} +array ( + 0 => + \Suit::Hearts, + 1 => + \Suit::Diamonds, + 2 => + \Suit::Clubs, + 3 => + \Suit::Spades, +)