diff options
author | Ilija Tovilo <ilija.tovilo@me.com> | 2020-06-10 23:10:18 +0200 |
---|---|---|
committer | Ilija Tovilo <ilija.tovilo@me.com> | 2021-03-17 19:08:03 +0100 |
commit | 269c8dac1d56ee85d71ae94d9b28dd7d8e8de7b7 (patch) | |
tree | 810ac41b2157ff4e8063f9696f97e1a9d77837c4 /ext | |
parent | a6fc427b8c51015c16541c112a26dd06bd75e99e (diff) | |
download | php-git-269c8dac1d56ee85d71ae94d9b28dd7d8e8de7b7.tar.gz |
Implement enums
RFC: https://wiki.php.net/rfc/enumerations
Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
Closes GH-6489.
Diffstat (limited to 'ext')
30 files changed, 1084 insertions, 31 deletions
diff --git a/ext/json/json.c b/ext/json/json.c index 5315966f7d..d8e17ef9da 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -187,6 +187,8 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ return "The decoded property name is invalid"; case PHP_JSON_ERROR_UTF16: return "Single unpaired UTF-16 surrogate in unicode escape"; + case PHP_JSON_ERROR_NON_BACKED_ENUM: + return "Non-backed enums have no default serialization"; default: return "Unknown error"; } diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index e78a6495e9..c76ddaf0fd 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -27,6 +27,7 @@ #include "php_json.h" #include "php_json_encoder.h" #include <zend_exceptions.h> +#include "zend_enum.h" static const char digits[] = "0123456789abcdef"; @@ -570,6 +571,19 @@ static int php_json_encode_serializable_object(smart_str *buf, zval *val, int op } /* }}} */ +static int php_json_encode_serializable_enum(smart_str *buf, zval *val, int options, php_json_encoder *encoder) +{ + zend_class_entry *ce = Z_OBJCE_P(val); + if (ce->enum_backing_type == IS_UNDEF) { + encoder->error_code = PHP_JSON_ERROR_NON_BACKED_ENUM; + smart_str_appendc(buf, '0'); + return FAILURE; + } + + zval *value_zv = zend_enum_fetch_case_value(Z_OBJ_P(val)); + return php_json_encode_zval(buf, value_zv, options, encoder); +} + int php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */ { again: @@ -606,6 +620,9 @@ again: if (instanceof_function(Z_OBJCE_P(val), php_json_serializable_ce)) { return php_json_encode_serializable_object(buf, val, options, encoder); } + if (Z_OBJCE_P(val)->ce_flags & ZEND_ACC_ENUM) { + return php_json_encode_serializable_enum(buf, val, options, encoder); + } /* fallthrough -- Non-serializable object */ case IS_ARRAY: { /* Avoid modifications (and potential freeing) of the array through a reference when a diff --git a/ext/json/php_json.h b/ext/json/php_json.h index f6d7b376bd..7d258805b2 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -50,7 +50,8 @@ typedef enum { PHP_JSON_ERROR_INF_OR_NAN, PHP_JSON_ERROR_UNSUPPORTED_TYPE, PHP_JSON_ERROR_INVALID_PROPERTY_NAME, - PHP_JSON_ERROR_UTF16 + PHP_JSON_ERROR_UTF16, + PHP_JSON_ERROR_NON_BACKED_ENUM, } php_json_error_code; /* json_decode() options */ diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 4c7611d36d..884ee796e0 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -637,12 +637,12 @@ static void zend_file_cache_serialize_func(zval *zv, zend_file_cache_metainfo *info, void *buf) { - zend_op_array *op_array; - + zend_function *func; SERIALIZE_PTR(Z_PTR_P(zv)); - op_array = Z_PTR_P(zv); - UNSERIALIZE_PTR(op_array); - zend_file_cache_serialize_op_array(op_array, script, info, buf); + func = Z_PTR_P(zv); + UNSERIALIZE_PTR(func); + ZEND_ASSERT(func->type == ZEND_USER_FUNCTION); + zend_file_cache_serialize_op_array(&func->op_array, script, info, buf); } static void zend_file_cache_serialize_prop_info(zval *zv, @@ -844,6 +844,14 @@ static void zend_file_cache_serialize_class(zval *zv, } } + if (ce->backed_enum_table) { + HashTable *ht; + SERIALIZE_PTR(ce->backed_enum_table); + ht = ce->backed_enum_table; + UNSERIALIZE_PTR(ht); + zend_file_cache_serialize_hash(ht, script, info, buf, zend_file_cache_serialize_zval); + } + SERIALIZE_PTR(ce->constructor); SERIALIZE_PTR(ce->destructor); SERIALIZE_PTR(ce->clone); @@ -1432,11 +1440,11 @@ static void zend_file_cache_unserialize_func(zval *zv, zend_persistent_script *script, void *buf) { - zend_op_array *op_array; - + zend_function *func; UNSERIALIZE_PTR(Z_PTR_P(zv)); - op_array = Z_PTR_P(zv); - zend_file_cache_unserialize_op_array(op_array, script, buf); + func = Z_PTR_P(zv); + ZEND_ASSERT(func->type == ZEND_USER_FUNCTION); + zend_file_cache_unserialize_op_array(&func->op_array, script, buf); } static void zend_file_cache_unserialize_prop_info(zval *zv, @@ -1615,6 +1623,12 @@ static void zend_file_cache_unserialize_class(zval *zv, } } + if (ce->backed_enum_table) { + UNSERIALIZE_PTR(ce->backed_enum_table); + zend_file_cache_unserialize_hash( + ce->backed_enum_table, script, buf, zend_file_cache_unserialize_zval, ZVAL_PTR_DTOR); + } + UNSERIALIZE_PTR(ce->constructor); UNSERIALIZE_PTR(ce->destructor); UNSERIALIZE_PTR(ce->clone); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 7ede974618..52d961c428 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -326,6 +326,26 @@ uint32_t zend_accel_get_type_map_ptr(zend_string *type_name, zend_class_entry *s return ret; } +static HashTable *zend_persist_backed_enum_table(HashTable *backed_enum_table) +{ + HashTable *ptr; + Bucket *p; + zend_hash_persist(backed_enum_table); + + ZEND_HASH_FOREACH_BUCKET(backed_enum_table, p) { + if (p->key != NULL) { + zend_accel_store_interned_string(p->key); + } + zend_persist_zval(&p->val); + } ZEND_HASH_FOREACH_END(); + + ptr = zend_shared_memdup_free(backed_enum_table, sizeof(HashTable)); + GC_SET_REFCOUNT(ptr, 2); + GC_TYPE_INFO(ptr) = GC_ARRAY | ((IS_ARRAY_IMMUTABLE|GC_NOT_COLLECTABLE) << GC_FLAGS_SHIFT); + + return ptr; +} + static void zend_persist_type(zend_type *type, zend_class_entry *scope) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list *list = ZEND_TYPE_LIST(*type); @@ -1050,6 +1070,10 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ce->trait_precedences, sizeof(zend_trait_precedence*) * (i + 1)); } } + + if (ce->backed_enum_table) { + ce->backed_enum_table = zend_persist_backed_enum_table(ce->backed_enum_table); + } } return ce; diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index ebd1c61358..c3292ec2a1 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -534,6 +534,18 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ADD_SIZE(sizeof(zend_trait_precedence*) * (i + 1)); } } + + if (ce->backed_enum_table) { + Bucket *p; + ADD_SIZE(sizeof(HashTable)); + zend_hash_persist_calc(ce->backed_enum_table); + ZEND_HASH_FOREACH_BUCKET(ce->backed_enum_table, p) { + if (p->key != NULL) { + ADD_INTERNED_STRING(p->key); + } + zend_persist_zval_calc(&p->val); + } ZEND_HASH_FOREACH_END(); + } } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 83d165beb2..280b3fc06c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -43,6 +43,7 @@ #include "zend_extensions.h" #include "zend_builtin_functions.h" #include "zend_smart_str.h" +#include "zend_enum.h" #include "php_reflection_arginfo.h" /* Key used to avoid leaking addresses in ReflectionProperty::getId() */ @@ -87,6 +88,9 @@ PHPAPI zend_class_entry *reflection_extension_ptr; PHPAPI zend_class_entry *reflection_zend_extension_ptr; PHPAPI zend_class_entry *reflection_reference_ptr; PHPAPI zend_class_entry *reflection_attribute_ptr; +PHPAPI zend_class_entry *reflection_enum_ptr; +PHPAPI zend_class_entry *reflection_enum_unit_case_ptr; +PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; /* Exception throwing macro */ #define _DO_THROW(msg) \ @@ -573,6 +577,9 @@ static void _class_const_string(smart_str *str, char *name, zend_class_constant if (Z_TYPE(c->value) == IS_ARRAY) { smart_str_append_printf(str, "%sConstant [ %s %s %s ] { Array }\n", indent, visibility, type, name); + } else if (Z_TYPE(c->value) == IS_OBJECT) { + smart_str_append_printf(str, "%sConstant [ %s %s %s ] { Object }\n", + indent, visibility, type, name); } else { zend_string *tmp_value_str; zend_string *value_str = zval_get_tmp_string(&c->value, &tmp_value_str); @@ -1251,6 +1258,18 @@ PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object) } /* }}} */ +static void zend_reflection_enum_factory(zend_class_entry *ce, zval *object) +{ + reflection_object *intern; + + reflection_instantiate(reflection_enum_ptr, object); + intern = Z_REFLECTION_P(object); + intern->ptr = ce; + intern->ref_type = REF_TYPE_OTHER; + intern->ce = ce; + ZVAL_STR_COPY(reflection_prop_name(object), ce->name); +} + /* {{{ reflection_extension_factory */ static void reflection_extension_factory(zval *object, const char *name_str) { @@ -1430,6 +1449,24 @@ static void reflection_class_constant_factory(zend_string *name_str, zend_class_ } /* }}} */ +static void reflection_enum_case_factory(zend_class_entry *ce, zend_string *name_str, zend_class_constant *constant, zval *object) +{ + reflection_object *intern; + + zend_class_entry *case_reflection_class = ce->backed_enum_table == IS_UNDEF + ? reflection_enum_unit_case_ptr + : reflection_enum_backed_case_ptr; + reflection_instantiate(case_reflection_class, object); + intern = Z_REFLECTION_P(object); + intern->ptr = constant; + intern->ref_type = REF_TYPE_CLASS_CONSTANT; + intern->ce = constant->ce; + intern->ignore_visibility = 0; + + ZVAL_STR_COPY(reflection_prop_name(object), name_str); + ZVAL_STR_COPY(reflection_prop_class(object), constant->ce->name); +} + static int get_parameter_default(zval *result, parameter_reference *param) { if (param->fptr->type == ZEND_INTERNAL_FUNCTION) { if (param->fptr->common.fn_flags & ZEND_ACC_USER_ARG_INFO) { @@ -3665,10 +3702,10 @@ ZEND_METHOD(ReflectionClassConstant, getValue) } GET_REFLECTION_OBJECT_PTR(ref); - ZVAL_COPY_OR_DUP(return_value, &ref->value); - if (Z_TYPE_P(return_value) == IS_CONSTANT_AST) { - zval_update_constant_ex(return_value, ref->ce); + if (Z_TYPE(ref->value) == IS_CONSTANT_AST) { + zval_update_constant_ex(&ref->value, ref->ce); } + ZVAL_COPY_OR_DUP(return_value, &ref->value); } /* }}} */ @@ -3718,6 +3755,16 @@ ZEND_METHOD(ReflectionClassConstant, getAttributes) } /* }}} */ +ZEND_METHOD(ReflectionClassConstant, isEnumCase) +{ + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + RETURN_BOOL(Z_ACCESS_FLAGS(ref->value) & ZEND_CLASS_CONST_IS_CASE); +} + /* {{{ reflection_class_object_ctor */ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_object) { @@ -4637,6 +4684,11 @@ ZEND_METHOD(ReflectionClass, isTrait) } /* }}} */ +ZEND_METHOD(ReflectionClass, isEnum) +{ + _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_ENUM); +} + /* {{{ Returns whether this class is final */ ZEND_METHOD(ReflectionClass, isFinal) { @@ -6494,6 +6546,194 @@ ZEND_METHOD(ReflectionAttribute, newInstance) RETURN_COPY_VALUE(&obj); } +ZEND_METHOD(ReflectionEnum, __construct) +{ + reflection_class_object_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + if (EG(exception)) { + RETURN_THROWS(); + } + + reflection_object *intern; + zend_class_entry *ce; + GET_REFLECTION_OBJECT_PTR(ce); + + if (!(ce->ce_flags & ZEND_ACC_ENUM)) { + zend_throw_exception_ex(reflection_exception_ptr, -1, "Class \"%s\" is not an enum", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } +} + +ZEND_METHOD(ReflectionEnum, hasCase) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + zend_class_constant *class_const = zend_hash_find_ptr(&ce->constants_table, name); + if (class_const == NULL) { + RETURN_FALSE; + } + + RETURN_BOOL(Z_ACCESS_FLAGS(class_const->value) & ZEND_CLASS_CONST_IS_CASE); +} + +ZEND_METHOD(ReflectionEnum, getCase) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + zend_class_constant *constant = zend_hash_find_ptr(&ce->constants_table, name); + if (constant == NULL) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "Case %s::%s does not exist", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + RETURN_THROWS(); + } + if (!(Z_ACCESS_FLAGS(constant->value) & ZEND_CLASS_CONST_IS_CASE)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "%s::%s is not a case", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + RETURN_THROWS(); + } + + reflection_enum_case_factory(ce, name, constant, return_value); +} + +ZEND_METHOD(ReflectionEnum, getCases) +{ + reflection_object *intern; + zend_class_entry *ce; + zend_string *name; + zend_class_constant *constant; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + array_init(return_value); + ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->constants_table, name, constant) { + if (Z_ACCESS_FLAGS(constant->value) & ZEND_CLASS_CONST_IS_CASE) { + zval class_const; + reflection_enum_case_factory(ce, name, constant, &class_const); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &class_const); + } + } ZEND_HASH_FOREACH_END(); +} + +ZEND_METHOD(ReflectionEnum, isBacked) +{ + reflection_object *intern; + zend_class_entry *ce; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + RETURN_BOOL(ce->enum_backing_type != IS_UNDEF); +} + +ZEND_METHOD(ReflectionEnum, getBackingType) +{ + reflection_object *intern; + zend_class_entry *ce; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + if (ce->enum_backing_type == IS_UNDEF) { + RETURN_NULL(); + } else { + zend_type type = ZEND_TYPE_INIT_CODE(ce->enum_backing_type, 0, 0); + reflection_type_factory(type, return_value, 0); + } +} + +ZEND_METHOD(ReflectionEnumUnitCase, __construct) +{ + ZEND_MN(ReflectionClassConstant___construct)(INTERNAL_FUNCTION_PARAM_PASSTHRU); + if (EG(exception)) { + RETURN_THROWS(); + } + + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (!(Z_ACCESS_FLAGS(ref->value) & ZEND_CLASS_CONST_IS_CASE)) { + zval *case_name = reflection_prop_name(ZEND_THIS); + zend_throw_exception_ex(reflection_exception_ptr, 0, "Constant %s::%s is not a case", ZSTR_VAL(ref->ce->name), Z_STRVAL_P(case_name)); + RETURN_THROWS(); + } +} + +ZEND_METHOD(ReflectionEnumUnitCase, getEnum) +{ + reflection_object *intern; + zend_class_constant *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(ref); + + zend_reflection_enum_factory(ref->ce, return_value); +} + +ZEND_METHOD(ReflectionEnumBackedCase, __construct) +{ + ZEND_MN(ReflectionEnumUnitCase___construct)(INTERNAL_FUNCTION_PARAM_PASSTHRU); + if (EG(exception)) { + RETURN_THROWS(); + } + + reflection_object *intern; + zend_class_constant *ref; + + GET_REFLECTION_OBJECT_PTR(ref); + + if (ref->ce->enum_backing_type == IS_UNDEF) { + zval *case_name = reflection_prop_name(ZEND_THIS); + zend_throw_exception_ex(reflection_exception_ptr, 0, "Enum case %s::%s is not a backed case", ZSTR_VAL(ref->ce->name), Z_STRVAL_P(case_name)); + RETURN_THROWS(); + } +} + +ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue) +{ + reflection_object *intern; + zend_class_constant *ref; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(ref); + + if (Z_TYPE(ref->value) == IS_CONSTANT_AST) { + zval_update_constant_ex(&ref->value, ref->ce); + } + + ZEND_ASSERT(intern->ce->enum_backing_type != IS_UNDEF); + zval *member_p = zend_enum_fetch_case_value(Z_OBJ(ref->value)); + + ZVAL_COPY_OR_DUP(return_value, member_p); +} + /* {{{ _reflection_write_property */ static zval *_reflection_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot) { @@ -6603,6 +6843,15 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_attribute_ptr = register_class_ReflectionAttribute(); reflection_init_class_handlers(reflection_attribute_ptr); + reflection_enum_ptr = register_class_ReflectionEnum(reflection_class_ptr); + reflection_init_class_handlers(reflection_enum_ptr); + + reflection_enum_unit_case_ptr = register_class_ReflectionEnumUnitCase(reflection_class_constant_ptr); + reflection_init_class_handlers(reflection_enum_unit_case_ptr); + + reflection_enum_backed_case_ptr = register_class_ReflectionEnumBackedCase(reflection_enum_unit_case_ptr); + reflection_init_class_handlers(reflection_enum_backed_case_ptr); + REGISTER_REFLECTION_CLASS_CONST_LONG(attribute, "IS_INSTANCEOF", REFLECTION_ATTRIBUTE_IS_INSTANCEOF); REFLECTION_G(key_initialized) = 0; diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h index 654ba55256..de368a86c0 100644 --- a/ext/reflection/php_reflection.h +++ b/ext/reflection/php_reflection.h @@ -43,6 +43,9 @@ extern PHPAPI zend_class_entry *reflection_extension_ptr; extern PHPAPI zend_class_entry *reflection_zend_extension_ptr; extern PHPAPI zend_class_entry *reflection_reference_ptr; extern PHPAPI zend_class_entry *reflection_attribute_ptr; +extern PHPAPI zend_class_entry *reflection_enum_ptr; +extern PHPAPI zend_class_entry *reflection_enum_unit_case_ptr; +extern PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; PHPAPI void zend_reflection_class_factory(zend_class_entry *ce, zval *object); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 9287ae7e9c..e9815558d6 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -298,6 +298,8 @@ class ReflectionClass implements Reflector /** @return bool */ public function isTrait() {} + public function isEnum(): bool {} + /** @return bool */ public function isAbstract() {} @@ -479,6 +481,8 @@ class ReflectionClassConstant implements Reflector /** @return ReflectionAttribute[] */ public function getAttributes(?string $name = null, int $flags = 0): array {} + + public function isEnumCase(): bool {} } class ReflectionParameter implements Reflector @@ -683,3 +687,36 @@ final class ReflectionAttribute private function __construct() {} } + +final class ReflectionEnum extends ReflectionClass +{ + public function __construct(object|string $objectOrClass) {} + + public function hasCase(string $name): bool {} + + public function getCase(string $name): ReflectionEnumUnitCase {} + + /** @return ReflectionEnumUnitCase[] */ + public function getCases(): array {} + + public function isBacked(): bool {} + + public function getBackingType(): ReflectionType|null {} +} + +class ReflectionEnumUnitCase extends ReflectionClassConstant +{ + public function __construct(object|string $class, string $constant) {} + + public function getEnum(): ReflectionEnum {} + + /** @implementation-alias ReflectionClassConstant::getValue */ + public function getValue(): UnitEnum {} +} + +final class ReflectionEnumBackedCase extends ReflectionEnumUnitCase +{ + public function __construct(object|string $class, string $constant) {} + + public function getBackingValue(): int|string {} +} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index a9df516fc2..36e6dc3cec 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: fb4e29d088862cc76d22c9902c79c86dbfa7ac95 */ + * Stub hash: 3594ec0b0c3ed7266223be9c6b426aac56e3aabe */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -218,6 +218,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isTrait arginfo_class_ReflectionFunctionAbstract_inNamespace +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_isEnum, 0, 0, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionClass_isAbstract arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionClass_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -308,8 +311,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isPromoted, 0, 0, _IS_BOOL, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionProperty_getModifiers arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -323,7 +325,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_hasType arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionProperty_isPromoted +#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionProperty_getDefaultValue arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -356,6 +358,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionClassConstant_isEnumCase arginfo_class_ReflectionClass_isEnum + #define arginfo_class_ReflectionParameter___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionParameter___construct, 0, 0, 2) @@ -401,7 +405,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionParameter_isVariadic arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionProperty_isPromoted +#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionParameter_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes @@ -478,7 +482,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionAttribute_getTarget, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionProperty_isPromoted +#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionClass_isEnum #define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionUnionType_getTypes @@ -489,6 +493,36 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionAttribute___construct arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionEnum___construct arginfo_class_ReflectionClass___construct + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionEnum_hasCase, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getCase, 0, 1, ReflectionEnumUnitCase, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionUnionType_getTypes + +#define arginfo_class_ReflectionEnum_isBacked arginfo_class_ReflectionClass_isEnum + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getBackingType, 0, 0, ReflectionType, 1) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionEnumUnitCase___construct arginfo_class_ReflectionClassConstant___construct + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnumUnitCase_getEnum, 0, 0, ReflectionEnum, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnumUnitCase_getValue, 0, 0, UnitEnum, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionEnumBackedCase___construct arginfo_class_ReflectionClassConstant___construct + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ReflectionEnumBackedCase_getBackingValue, 0, 0, MAY_BE_LONG|MAY_BE_STRING) +ZEND_END_ARG_INFO() + ZEND_METHOD(Reflection, getModifierNames); ZEND_METHOD(ReflectionClass, __clone); @@ -579,6 +613,7 @@ ZEND_METHOD(ReflectionClass, getTraits); ZEND_METHOD(ReflectionClass, getTraitNames); ZEND_METHOD(ReflectionClass, getTraitAliases); ZEND_METHOD(ReflectionClass, isTrait); +ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); ZEND_METHOD(ReflectionClass, isFinal); ZEND_METHOD(ReflectionClass, getModifiers); @@ -633,6 +668,7 @@ ZEND_METHOD(ReflectionClassConstant, getModifiers); ZEND_METHOD(ReflectionClassConstant, getDeclaringClass); ZEND_METHOD(ReflectionClassConstant, getDocComment); ZEND_METHOD(ReflectionClassConstant, getAttributes); +ZEND_METHOD(ReflectionClassConstant, isEnumCase); ZEND_METHOD(ReflectionParameter, __construct); ZEND_METHOD(ReflectionParameter, __toString); ZEND_METHOD(ReflectionParameter, getName); @@ -690,6 +726,16 @@ ZEND_METHOD(ReflectionAttribute, getArguments); ZEND_METHOD(ReflectionAttribute, newInstance); ZEND_METHOD(ReflectionAttribute, __clone); ZEND_METHOD(ReflectionAttribute, __construct); +ZEND_METHOD(ReflectionEnum, __construct); +ZEND_METHOD(ReflectionEnum, hasCase); +ZEND_METHOD(ReflectionEnum, getCase); +ZEND_METHOD(ReflectionEnum, getCases); +ZEND_METHOD(ReflectionEnum, isBacked); +ZEND_METHOD(ReflectionEnum, getBackingType); +ZEND_METHOD(ReflectionEnumUnitCase, __construct); +ZEND_METHOD(ReflectionEnumUnitCase, getEnum); +ZEND_METHOD(ReflectionEnumBackedCase, __construct); +ZEND_METHOD(ReflectionEnumBackedCase, getBackingValue); static const zend_function_entry class_ReflectionException_methods[] = { @@ -818,6 +864,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, getTraitNames, arginfo_class_ReflectionClass_getTraitNames, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getTraitAliases, arginfo_class_ReflectionClass_getTraitAliases, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isTrait, arginfo_class_ReflectionClass_isTrait, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isEnum, arginfo_class_ReflectionClass_isEnum, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isAbstract, arginfo_class_ReflectionClass_isAbstract, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isFinal, arginfo_class_ReflectionClass_isFinal, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getModifiers, arginfo_class_ReflectionClass_getModifiers, ZEND_ACC_PUBLIC) @@ -890,6 +937,7 @@ static const zend_function_entry class_ReflectionClassConstant_methods[] = { ZEND_ME(ReflectionClassConstant, getDeclaringClass, arginfo_class_ReflectionClassConstant_getDeclaringClass, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getDocComment, arginfo_class_ReflectionClassConstant_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassConstant, getAttributes, arginfo_class_ReflectionClassConstant_getAttributes, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassConstant, isEnumCase, arginfo_class_ReflectionClassConstant_isEnumCase, ZEND_ACC_PUBLIC) ZEND_FE_END }; @@ -995,6 +1043,32 @@ static const zend_function_entry class_ReflectionAttribute_methods[] = { ZEND_FE_END }; + +static const zend_function_entry class_ReflectionEnum_methods[] = { + ZEND_ME(ReflectionEnum, __construct, arginfo_class_ReflectionEnum___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, hasCase, arginfo_class_ReflectionEnum_hasCase, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, getCase, arginfo_class_ReflectionEnum_getCase, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, getCases, arginfo_class_ReflectionEnum_getCases, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, isBacked, arginfo_class_ReflectionEnum_isBacked, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnum, getBackingType, arginfo_class_ReflectionEnum_getBackingType, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_ReflectionEnumUnitCase_methods[] = { + ZEND_ME(ReflectionEnumUnitCase, __construct, arginfo_class_ReflectionEnumUnitCase___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnumUnitCase, getEnum, arginfo_class_ReflectionEnumUnitCase_getEnum, ZEND_ACC_PUBLIC) + ZEND_MALIAS(ReflectionClassConstant, getValue, getValue, arginfo_class_ReflectionEnumUnitCase_getValue, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + +static const zend_function_entry class_ReflectionEnumBackedCase_methods[] = { + ZEND_ME(ReflectionEnumBackedCase, __construct, arginfo_class_ReflectionEnumBackedCase___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionEnumBackedCase, getBackingValue, arginfo_class_ReflectionEnumBackedCase_getBackingValue, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_ReflectionException(zend_class_entry *class_entry_Exception) { zend_class_entry ce, *class_entry; @@ -1258,3 +1332,35 @@ static zend_class_entry *register_class_ReflectionAttribute(void) return class_entry; } + +static zend_class_entry *register_class_ReflectionEnum(zend_class_entry *class_entry_ReflectionClass) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionEnum", class_ReflectionEnum_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionClass); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + return class_entry; +} + +static zend_class_entry *register_class_ReflectionEnumUnitCase(zend_class_entry *class_entry_ReflectionClassConstant) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionEnumUnitCase", class_ReflectionEnumUnitCase_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionClassConstant); + + return class_entry; +} + +static zend_class_entry *register_class_ReflectionEnumBackedCase(zend_class_entry *class_entry_ReflectionEnumUnitCase) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionEnumBackedCase", class_ReflectionEnumBackedCase_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionEnumUnitCase); + class_entry->ce_flags |= ZEND_ACC_FINAL; + + return class_entry; +} diff --git a/ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt b/ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt new file mode 100644 index 0000000000..7125075085 --- /dev/null +++ b/ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt @@ -0,0 +1,23 @@ +--TEST-- +ReflectionClassConstant::isEnumCase() +--FILE-- +<?php + +enum Foo { + case Bar; + const Baz = self::Bar; +} + +class Qux { + const Quux = 0; +} + +var_dump((new ReflectionClassConstant(Foo::class, 'Bar'))->isEnumCase()); +var_dump((new ReflectionClassConstant(Foo::class, 'Baz'))->isEnumCase()); +var_dump((new ReflectionClassConstant(Qux::class, 'Quux'))->isEnumCase()); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) diff --git a/ext/reflection/tests/ReflectionClass_isEnum.phpt b/ext/reflection/tests/ReflectionClass_isEnum.phpt new file mode 100644 index 0000000000..b781c61509 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_isEnum.phpt @@ -0,0 +1,20 @@ +--TEST-- +Testing ReflectionClass::isEnum() +--FILE-- +<?php + +class Foo {} +enum Bar { + case Baz; +} + +$fooReflection = new ReflectionClass(Foo::class); +$barReflection = new ReflectionClass(Bar::class); + +var_dump($fooReflection->isEnum()); +var_dump($barReflection->isEnum()); + +?> +--EXPECT-- +bool(false) +bool(true) diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index e218e8b470..e8baf72689 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -27,7 +27,7 @@ Class [ <internal:Reflection> class ReflectionClass implements Reflector, String Property [ public string $name ] } - - Methods [54] { + - Methods [55] { Method [ <internal:Reflection> final private method __clone ] { - Parameters [0] { @@ -234,6 +234,13 @@ Class [ <internal:Reflection> class ReflectionClass implements Reflector, String } } + Method [ <internal:Reflection> public method isEnum ] { + + - Parameters [0] { + } + - Return [ bool ] + } + Method [ <internal:Reflection> public method isAbstract ] { - Parameters [0] { diff --git a/ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt b/ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt new file mode 100644 index 0000000000..f81241d73a --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt @@ -0,0 +1,38 @@ +--TEST-- +ReflectionEnumBackedCase::getBackingValue() +--FILE-- +<?php + +enum Enum_ { + case Foo; +} + +enum IntEnum: int { + case Foo = 0; +} + +enum StringEnum: string { + case Foo = 'Foo'; +} + +try { + var_dump(new ReflectionEnumBackedCase(Enum_::class, 'Foo')); +} catch (ReflectionException $e) { + echo $e->getMessage() . "\n"; +} + +try { + var_dump(new ReflectionEnumBackedCase([], 'Foo')); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +var_dump((new ReflectionEnumBackedCase(IntEnum::class, 'Foo'))->getBackingValue()); +var_dump((new ReflectionEnumBackedCase(StringEnum::class, 'Foo'))->getBackingValue()); + +?> +--EXPECT-- +Enum case Enum_::Foo is not a backed case +ReflectionEnumBackedCase::__construct(): Argument #1 ($class) must be of type object|string, array given +int(0) +string(3) "Foo" diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt new file mode 100644 index 0000000000..de5af5c549 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt @@ -0,0 +1,36 @@ +--TEST-- +ReflectionEnumUnitCase::__construct() +--FILE-- +<?php + +enum Foo { + case Bar; + const Baz = self::Bar; +} + +echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getName() . "\n"; + +try { + new ReflectionEnumUnitCase(Foo::class, 'Baz'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnumUnitCase(Foo::class, 'Qux'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnumUnitCase([], 'Foo'); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Bar +Constant Foo::Baz is not a case +Constant Foo::Qux does not exist +ReflectionEnumUnitCase::__construct(): Argument #1 ($class) must be of type object|string, array given diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt new file mode 100644 index 0000000000..3d3bcc227e --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt @@ -0,0 +1,39 @@ +--TEST-- +ReflectionEnumUnitCase::getEnum() +--FILE-- +<?php + +enum Foo { + case Bar; +} + +echo (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getEnum(); + +?> +--EXPECTF-- +Class [ <user> final class Foo implements UnitEnum ] { + @@ %sReflectionEnumUnitCase_getEnum.php 3-5 + + - Constants [1] { + Constant [ public Foo Bar ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ <internal, prototype UnitEnum> static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public string $name ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt b/ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt new file mode 100644 index 0000000000..ec5f22d9f8 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt @@ -0,0 +1,30 @@ +--TEST-- +ReflectionEnumUnitCase::getValue() +--FILE-- +<?php + +enum Foo { + case Bar; + case Baz; +} + +$barFromReflection = (new ReflectionEnumUnitCase(Foo::class, 'Bar'))->getValue(); +$bazFromReflection = (new ReflectionEnumUnitCase(Foo::class, 'Baz'))->getValue(); + +var_dump($barFromReflection); +var_dump($bazFromReflection); + +var_dump(Foo::Bar === $barFromReflection); +var_dump(Foo::Baz === $barFromReflection); + +var_dump(Foo::Bar === $bazFromReflection); +var_dump(Foo::Baz === $bazFromReflection); + +?> +--EXPECT-- +enum(Foo::Bar) +enum(Foo::Baz) +bool(true) +bool(false) +bool(false) +bool(true) diff --git a/ext/reflection/tests/ReflectionEnum_construct.phpt b/ext/reflection/tests/ReflectionEnum_construct.phpt new file mode 100644 index 0000000000..4b4a039105 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_construct.phpt @@ -0,0 +1,34 @@ +--TEST-- +ReflectionEnum::__construct() +--FILE-- +<?php + +enum Foo {} +class Bar {} + +echo (new ReflectionEnum(Foo::class))->getName() . "\n"; + +try { + new ReflectionEnum('Bar'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnum('Baz'); +} catch (\Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + new ReflectionEnum([]); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Foo +Class "Bar" is not an enum +Class "Baz" does not exist +ReflectionEnum::__construct(): Argument #1 ($objectOrClass) must be of type object|string, array given diff --git a/ext/reflection/tests/ReflectionEnum_getBackingType.phpt b/ext/reflection/tests/ReflectionEnum_getBackingType.phpt new file mode 100644 index 0000000000..e9d429f592 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_getBackingType.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionEnum::getBackingType() +--FILE-- +<?php + +enum Enum_ {} +enum IntEnum: int {} +enum StringEnum: string {} + +function test(): string {} + +var_dump((new ReflectionEnum(Enum_::class))->getBackingType()); +echo (new ReflectionEnum(IntEnum::class))->getBackingType() . "\n"; +echo (new ReflectionEnum(StringEnum::class))->getBackingType() . "\n"; + +?> +--EXPECT-- +NULL +int +string diff --git a/ext/reflection/tests/ReflectionEnum_getCase.phpt b/ext/reflection/tests/ReflectionEnum_getCase.phpt new file mode 100644 index 0000000000..f2b53cf657 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_getCase.phpt @@ -0,0 +1,50 @@ +--TEST-- +ReflectionEnum::getCases() +--FILE-- +<?php + +enum Enum_ { + case Foo; + const Bar = self::Foo; +} + +enum IntEnum: int { + case Foo = 0; + const Bar = self::Foo; +} + +function test(string $enumName, string $caseName) { + try { + $reflectionEnum = new ReflectionEnum($enumName); + var_dump($reflectionEnum->getCase($caseName)); + } catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; + } +} + +test(Enum_::class, 'Foo'); +test(Enum_::class, 'Bar'); +test(Enum_::class, 'Baz'); + +test(IntEnum::class, 'Foo'); +test(IntEnum::class, 'Bar'); +test(IntEnum::class, 'Baz'); + +?> +--EXPECT-- +object(ReflectionEnumUnitCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(5) "Enum_" +} +ReflectionException: Enum_::Bar is not a case +ReflectionException: Case Enum_::Baz does not exist +object(ReflectionEnumBackedCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(7) "IntEnum" +} +ReflectionException: IntEnum::Bar is not a case +ReflectionException: Case IntEnum::Baz does not exist diff --git a/ext/reflection/tests/ReflectionEnum_getCases.phpt b/ext/reflection/tests/ReflectionEnum_getCases.phpt new file mode 100644 index 0000000000..9365008774 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_getCases.phpt @@ -0,0 +1,54 @@ +--TEST-- +ReflectionEnum::getCases() +--FILE-- +<?php + +enum Enum_ { + case Foo; + case Bar; + const Baz = self::Bar; +} + +enum IntEnum: int { + case Foo = 0; + case Bar = 1; + const Baz = self::Bar; +} + +var_dump((new ReflectionEnum(Enum_::class))->getCases()); +var_dump((new ReflectionEnum(IntEnum::class))->getCases()); + +?> +--EXPECT-- +array(2) { + [0]=> + object(ReflectionEnumUnitCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(5) "Enum_" + } + [1]=> + object(ReflectionEnumUnitCase)#3 (2) { + ["name"]=> + string(3) "Bar" + ["class"]=> + string(5) "Enum_" + } +} +array(2) { + [0]=> + object(ReflectionEnumBackedCase)#2 (2) { + ["name"]=> + string(3) "Foo" + ["class"]=> + string(7) "IntEnum" + } + [1]=> + object(ReflectionEnumBackedCase)#1 (2) { + ["name"]=> + string(3) "Bar" + ["class"]=> + string(7) "IntEnum" + } +} diff --git a/ext/reflection/tests/ReflectionEnum_hasCase.phpt b/ext/reflection/tests/ReflectionEnum_hasCase.phpt new file mode 100644 index 0000000000..3310f0ef6b --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_hasCase.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionEnum::hasCase() +--FILE-- +<?php + +enum Foo { + case Bar; + const Baz = self::Bar; +} + +$reflectionEnum = new ReflectionEnum(Foo::class); +var_dump($reflectionEnum->hasCase('Bar')); +var_dump($reflectionEnum->hasCase('Baz')); +var_dump($reflectionEnum->hasCase('Qux')); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) diff --git a/ext/reflection/tests/ReflectionEnum_isBacked.phpt b/ext/reflection/tests/ReflectionEnum_isBacked.phpt new file mode 100644 index 0000000000..cb449e951a --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_isBacked.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionEnum::isBacked() +--FILE-- +<?php + +enum Enum_ {} +enum IntEnum: int {} +enum StringEnum: string {} + +function test(): string {} + +var_dump((new ReflectionEnum(Enum_::class))->isBacked()); +var_dump((new ReflectionEnum(IntEnum::class))->isBacked()); +var_dump((new ReflectionEnum(StringEnum::class))->isBacked()); + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) diff --git a/ext/reflection/tests/ReflectionEnum_toString.phpt b/ext/reflection/tests/ReflectionEnum_toString.phpt new file mode 100644 index 0000000000..567191e594 --- /dev/null +++ b/ext/reflection/tests/ReflectionEnum_toString.phpt @@ -0,0 +1,39 @@ +--TEST-- +ReflectionEnum::__toString() +--FILE-- +<?php + +enum Foo { + case Bar; +} + +echo new ReflectionEnum(Foo::class); + +?> +--EXPECTF-- +Class [ <user> final class Foo implements UnitEnum ] { + @@ %sReflectionEnum_toString.php 3-5 + + - Constants [1] { + Constant [ public Foo Bar ] { Object } + } + + - Static properties [0] { + } + + - Static methods [1] { + Method [ <internal, prototype UnitEnum> static public method cases ] { + + - Parameters [0] { + } + - Return [ array ] + } + } + + - Properties [1] { + Property [ public string $name ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 81d3c8d55b..084e05e118 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECT-- -array(19) { +array(22) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -104,4 +104,19 @@ array(19) { ["name"]=> string(19) "ReflectionAttribute" } + ["ReflectionEnum"]=> + object(ReflectionClass)#21 (1) { + ["name"]=> + string(14) "ReflectionEnum" + } + ["ReflectionEnumUnitCase"]=> + object(ReflectionClass)#22 (1) { + ["name"]=> + string(22) "ReflectionEnumUnitCase" + } + ["ReflectionEnumBackedCase"]=> + object(ReflectionClass)#23 (1) { + ["name"]=> + string(24) "ReflectionEnumBackedCase" + } } diff --git a/ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt b/ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt new file mode 100644 index 0000000000..6efea38913 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt @@ -0,0 +1,23 @@ +--TEST-- +Test ReflectionProperty::setValue() error cases. +--FILE-- +<?php + +enum Foo: int { + case Bar = 0; +} + +$reflection = new ReflectionProperty(Foo::class, 'value'); + +try { + $reflection->setValue(Foo::Bar, 1); +} catch (Error $e) { + echo $e->getMessage() . "\n"; +} + +var_dump(Foo::Bar->value); + +?> +--EXPECT-- +Enum properties are immutable +int(0) diff --git a/ext/reflection/tests/bug36337.phpt b/ext/reflection/tests/bug36337.phpt index ebe64431a7..d4a155bda9 100644 --- a/ext/reflection/tests/bug36337.phpt +++ b/ext/reflection/tests/bug36337.phpt @@ -3,7 +3,7 @@ Reflection Bug #36337 (ReflectionProperty fails to return correct visibility) --FILE-- <?php -abstract class enum { +abstract class enum_ { protected $_values; public function __construct() { @@ -13,7 +13,7 @@ abstract class enum { } -final class myEnum extends enum { +final class myEnum extends enum_ { public $_values = array( 0 => 'No value', ); diff --git a/ext/standard/var.c b/ext/standard/var.c index 06b98b5b9d..bcc6cf5a88 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -26,6 +26,7 @@ #include "zend_smart_str.h" #include "basic_functions.h" #include "php_incomplete_class.h" +#include "zend_enum.h" /* }}} */ struct php_serialize_data { @@ -144,7 +145,14 @@ again: } PUTS("}\n"); break; - case IS_OBJECT: + case IS_OBJECT: { + zend_class_entry *ce = Z_OBJCE_P(struc); + if (ce->ce_flags & ZEND_ACC_ENUM) { + zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); + php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval)); + return; + } + if (Z_IS_RECURSIVE_P(struc)) { PUTS("*RECURSION*\n"); return; @@ -183,6 +191,7 @@ again: PUTS("}\n"); Z_UNPROTECT_RECURSION_P(struc); break; + } case IS_RESOURCE: { const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc)); php_printf("%sresource(%d) of type (%s)\n", COMMON, Z_RES_P(struc)->handle, type_name ? type_name : "Unknown"); @@ -568,27 +577,39 @@ again: buffer_append_spaces(buf, level - 1); } + zend_class_entry *ce = Z_OBJCE_P(struc); + bool is_enum = ce->ce_flags & ZEND_ACC_ENUM; + /* stdClass has no __set_state method, but can be casted to */ - if (Z_OBJCE_P(struc) == zend_standard_class_def) { + if (ce == zend_standard_class_def) { smart_str_appendl(buf, "(object) array(\n", 16); } else { - smart_str_append(buf, Z_OBJCE_P(struc)->name); - smart_str_appendl(buf, "::__set_state(array(\n", 21); + smart_str_append(buf, ce->name); + if (is_enum) { + zend_object *zobj = Z_OBJ_P(struc); + zval *case_name_zval = zend_enum_fetch_case_name(zobj); + smart_str_appendl(buf, "::", 2); + smart_str_append(buf, Z_STR_P(case_name_zval)); + } else { + smart_str_appendl(buf, "::__set_state(array(\n", 21); + } } - if (myht) { + if (myht && !is_enum) { ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, val) { php_object_element_export(val, index, key, level, buf); } ZEND_HASH_FOREACH_END(); GC_TRY_UNPROTECT_RECURSION(myht); + } + if (myht) { zend_release_properties(myht); } - if (level > 1) { + if (level > 1 && !is_enum) { buffer_append_spaces(buf, level - 1); } - if (Z_OBJCE_P(struc) == zend_standard_class_def) { + if (ce == zend_standard_class_def) { smart_str_appendc(buf, ')'); - } else { + } else if (!is_enum) { smart_str_appendl(buf, "))", 2); } @@ -1048,6 +1069,23 @@ again: bool incomplete_class; uint32_t count; + if (ce->ce_flags & ZEND_ACC_ENUM) { + PHP_CLASS_ATTRIBUTES; + + zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); + + PHP_SET_CLASS_ATTRIBUTES(struc); + smart_str_appendl(buf, "E:", 2); + smart_str_append_unsigned(buf, ZSTR_LEN(class_name) + strlen(":") + Z_STRLEN_P(case_name_zval)); + smart_str_appendl(buf, ":\"", 2); + smart_str_append(buf, class_name); + smart_str_appendc(buf, ':'); + smart_str_append(buf, Z_STR_P(case_name_zval)); + smart_str_appendl(buf, "\";", 2); + PHP_CLEANUP_CLASS_ATTRIBUTES(); + return; + } + if (ce->__serialize) { zval retval, obj; zend_string *key; diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index a14e34125d..d3a3b91ede 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -1309,6 +1309,86 @@ object ":" uiv ":" ["] { return object_common(UNSERIALIZE_PASSTHRU, elements, has_unserialize); } +"E:" uiv ":" ["] { + if (!var_hash) return 0; + + size_t len = parse_uiv(start + 2); + size_t maxlen = max - YYCURSOR; + if (maxlen < len || len == 0) { + *p = start + 2; + return 0; + } + + char *str = (char *) YYCURSOR; + YYCURSOR += len; + + if (*(YYCURSOR) != '"') { + *p = YYCURSOR; + return 0; + } + if (*(YYCURSOR+1) != ';') { + *p = YYCURSOR+1; + return 0; + } + + char *colon_ptr = memchr(str, ':', len); + if (colon_ptr == NULL) { + php_error_docref(NULL, E_WARNING, "Invalid enum name '%.*s' (missing colon)", (int) len, str); + return 0; + } + size_t colon_pos = colon_ptr - str; + + zend_string *enum_name = zend_string_init(str, colon_pos, 0); + zend_string *case_name = zend_string_init(&str[colon_pos + 1], len - colon_pos - 1, 0); + + if (!zend_is_valid_class_name(enum_name)) { + goto fail; + } + + zend_class_entry *ce = zend_lookup_class(enum_name); + if (!ce) { + php_error_docref(NULL, E_WARNING, "Class '%s' not found", ZSTR_VAL(enum_name)); + goto fail; + } + if (!(ce->ce_flags & ZEND_ACC_ENUM)) { + php_error_docref(NULL, E_WARNING, "Class '%s' is not an enum", ZSTR_VAL(enum_name)); + goto fail; + } + + YYCURSOR += 2; + *p = YYCURSOR; + + zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), case_name); + if (!c) { + php_error_docref(NULL, E_WARNING, "Undefined constant %s::%s", ZSTR_VAL(enum_name), ZSTR_VAL(case_name)); + goto fail; + } + + if (!(Z_ACCESS_FLAGS(c->value) & ZEND_CLASS_CONST_IS_CASE)) { + php_error_docref(NULL, E_WARNING, "%s::%s is not an enum case", ZSTR_VAL(enum_name), ZSTR_VAL(case_name)); + goto fail; + } + + zend_string_release_ex(enum_name, 0); + zend_string_release_ex(case_name, 0); + + zval *value = &c->value; + if (Z_TYPE_P(value) == IS_CONSTANT_AST) { + if (zval_update_constant_ex(value, c->ce) == FAILURE) { + return 0; + } + } + ZEND_ASSERT(Z_TYPE_P(value) == IS_OBJECT); + ZVAL_COPY(rval, value); + + return 1; + +fail: + zend_string_release_ex(enum_name, 0); + zend_string_release_ex(case_name, 0); + return 0; +} + "}" { /* this is the case where we have less data than planned */ php_error_docref(NULL, E_NOTICE, "Unexpected end of serialized data"); diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 10663442c4..96571bb1d7 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -134,6 +134,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_CLASS", T_CLASS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_TRAIT", T_TRAIT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INTERFACE", T_INTERFACE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EXTENDS", T_EXTENDS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IMPLEMENTS", T_IMPLEMENTS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAMESPACE", T_NAMESPACE, CONST_CS | CONST_PERSISTENT); @@ -283,6 +284,7 @@ char *get_token_type_name(int token_type) case T_CLASS: return "T_CLASS"; case T_TRAIT: return "T_TRAIT"; case T_INTERFACE: return "T_INTERFACE"; + case T_ENUM: return "T_ENUM"; case T_EXTENDS: return "T_EXTENDS"; case T_IMPLEMENTS: return "T_IMPLEMENTS"; case T_NAMESPACE: return "T_NAMESPACE"; |