summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorIlija Tovilo <ilija.tovilo@me.com>2020-06-10 23:10:18 +0200
committerIlija Tovilo <ilija.tovilo@me.com>2021-03-17 19:08:03 +0100
commit269c8dac1d56ee85d71ae94d9b28dd7d8e8de7b7 (patch)
tree810ac41b2157ff4e8063f9696f97e1a9d77837c4 /ext
parenta6fc427b8c51015c16541c112a26dd06bd75e99e (diff)
downloadphp-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')
-rw-r--r--ext/json/json.c2
-rw-r--r--ext/json/json_encoder.c17
-rw-r--r--ext/json/php_json.h3
-rw-r--r--ext/opcache/zend_file_cache.c32
-rw-r--r--ext/opcache/zend_persist.c24
-rw-r--r--ext/opcache/zend_persist_calc.c12
-rw-r--r--ext/reflection/php_reflection.c255
-rw-r--r--ext/reflection/php_reflection.h3
-rw-r--r--ext/reflection/php_reflection.stub.php37
-rw-r--r--ext/reflection/php_reflection_arginfo.h118
-rw-r--r--ext/reflection/tests/ReflectionClassConstant_isEnumCase.phpt23
-rw-r--r--ext/reflection/tests/ReflectionClass_isEnum.phpt20
-rw-r--r--ext/reflection/tests/ReflectionClass_toString_001.phpt9
-rw-r--r--ext/reflection/tests/ReflectionEnumBackedCase_getBackingValue.phpt38
-rw-r--r--ext/reflection/tests/ReflectionEnumUnitCase_construct.phpt36
-rw-r--r--ext/reflection/tests/ReflectionEnumUnitCase_getEnum.phpt39
-rw-r--r--ext/reflection/tests/ReflectionEnumUnitCase_getValue.phpt30
-rw-r--r--ext/reflection/tests/ReflectionEnum_construct.phpt34
-rw-r--r--ext/reflection/tests/ReflectionEnum_getBackingType.phpt20
-rw-r--r--ext/reflection/tests/ReflectionEnum_getCase.phpt50
-rw-r--r--ext/reflection/tests/ReflectionEnum_getCases.phpt54
-rw-r--r--ext/reflection/tests/ReflectionEnum_hasCase.phpt20
-rw-r--r--ext/reflection/tests/ReflectionEnum_isBacked.phpt20
-rw-r--r--ext/reflection/tests/ReflectionEnum_toString.phpt39
-rw-r--r--ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt17
-rw-r--r--ext/reflection/tests/ReflectionProperty_setValue_readonly.phpt23
-rw-r--r--ext/reflection/tests/bug36337.phpt4
-rw-r--r--ext/standard/var.c54
-rw-r--r--ext/standard/var_unserializer.re80
-rw-r--r--ext/tokenizer/tokenizer_data.c2
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";