diff --git a/Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt b/Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt new file mode 100644 index 0000000000000..bd04220447c78 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_duplicated_modifier.phpt @@ -0,0 +1,12 @@ +--TEST-- +The readonly class modifier can only be added once +--FILE-- + +--EXPECTF-- +Fatal error: Multiple readonly modifiers are not allowed in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt b/Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt new file mode 100644 index 0000000000000..b4da25ab0fec8 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_dynamic_property.phpt @@ -0,0 +1,20 @@ +--TEST-- +Readonly classes cannot use dynamic properties +--FILE-- +bar = 1; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot create dynamic property Foo::$bar diff --git a/Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt b/Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt new file mode 100644 index 0000000000000..ace50c572aced --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_dynamic_property_attribute.phpt @@ -0,0 +1,13 @@ +--TEST-- +Readonly classes cannot apply the #[AllowDynamicProperties] attribute +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_final_modifier.phpt b/Zend/tests/readonly_classes/readonly_class_final_modifier.phpt new file mode 100644 index 0000000000000..e530534c9f585 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_final_modifier.phpt @@ -0,0 +1,16 @@ +--TEST-- +The readonly and final class modifiers can be defined in the same time +--FILE-- + +--EXPECTF-- +Fatal error: Class Bar cannot extend final class Foo in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt b/Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt new file mode 100644 index 0000000000000..2d76d844cfa49 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_inheritance_error1.phpt @@ -0,0 +1,16 @@ +--TEST-- +Non-readonly class cannot extend a readonly class +--FILE-- + +--EXPECTF-- +Fatal error: Non-readonly class Bar cannot extend readonly class Foo in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt b/Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt new file mode 100644 index 0000000000000..0996653681e5c --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_inheritance_error2.phpt @@ -0,0 +1,16 @@ +--TEST-- +Readonly class cannot extend a non-readonly class +--FILE-- + +--EXPECTF-- +Fatal error: Readonly class Bar cannot extend non-readonly class Foo in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt b/Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt new file mode 100644 index 0000000000000..33e73554ce017 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_inheritance_success.phpt @@ -0,0 +1,15 @@ +--TEST-- +Readonly class can extend a readonly class +--FILE-- + +--EXPECT-- diff --git a/Zend/tests/readonly_classes/readonly_class_missing_type1.phpt b/Zend/tests/readonly_classes/readonly_class_missing_type1.phpt new file mode 100644 index 0000000000000..ef4d7c0af786b --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_missing_type1.phpt @@ -0,0 +1,13 @@ +--TEST-- +Normal properties of a readonly class must have type +--FILE-- + +--EXPECTF-- +Fatal error: Readonly property Foo::$bar must have type in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_missing_type2.phpt b/Zend/tests/readonly_classes/readonly_class_missing_type2.phpt new file mode 100644 index 0000000000000..38fbd7222f7da --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_missing_type2.phpt @@ -0,0 +1,15 @@ +--TEST-- +Promoted properties of a readonly class must have type +--FILE-- + +--EXPECTF-- +Fatal error: Readonly property Foo::$bar must have type in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_class_property1.phpt b/Zend/tests/readonly_classes/readonly_class_property1.phpt new file mode 100644 index 0000000000000..34e09eecd7fc2 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_property1.phpt @@ -0,0 +1,25 @@ +--TEST-- +Normal properties of a readonly class are implicitly declared as readonly +--FILE-- +bar = 1; + } +} + +$foo = new Foo(); + +try { + $foo->bar = 2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot modify readonly property Foo::$bar diff --git a/Zend/tests/readonly_classes/readonly_class_property2.phpt b/Zend/tests/readonly_classes/readonly_class_property2.phpt new file mode 100644 index 0000000000000..6db08f4dfb389 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_property2.phpt @@ -0,0 +1,23 @@ +--TEST-- +Promoted properties of a readonly class are implicitly declared as readonly +--FILE-- +bar = 2; +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Cannot modify readonly property Foo::$bar diff --git a/Zend/tests/readonly_classes/readonly_class_property3.phpt b/Zend/tests/readonly_classes/readonly_class_property3.phpt new file mode 100644 index 0000000000000..e121f4e49ee77 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_class_property3.phpt @@ -0,0 +1,13 @@ +--TEST-- +Declaring static property for a readonly class is forbidden +--FILE-- + +--EXPECTF-- +Fatal error: Readonly class Foo cannot declare static properties in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_enum.phpt b/Zend/tests/readonly_classes/readonly_enum.phpt new file mode 100644 index 0000000000000..afc1f235ce2da --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_enum.phpt @@ -0,0 +1,12 @@ +--TEST-- +Enums cannot be readonly +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "enum", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_interface.phpt b/Zend/tests/readonly_classes/readonly_interface.phpt new file mode 100644 index 0000000000000..c6fd42d5adca9 --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_interface.phpt @@ -0,0 +1,12 @@ +--TEST-- +Interfaces cannot be readonly +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "interface", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d diff --git a/Zend/tests/readonly_classes/readonly_trait.phpt b/Zend/tests/readonly_classes/readonly_trait.phpt new file mode 100644 index 0000000000000..0e18d14bf8dcd --- /dev/null +++ b/Zend/tests/readonly_classes/readonly_trait.phpt @@ -0,0 +1,12 @@ +--TEST-- +Traits cannot be readonly +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "trait", expecting "abstract" or "final" or "readonly" or "class" in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 8f248b464c9b3..e996a3f26ab2c 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1715,6 +1715,9 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio if (decl->flags & ZEND_ACC_FINAL) { smart_str_appends(str, "final "); } + if (decl->flags & ZEND_ACC_READONLY_CLASS) { + smart_str_appends(str, "readonly "); + } smart_str_appends(str, "class "); } smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name)); diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 433586f55f08c..6330887b29084 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -71,6 +71,11 @@ static void validate_allow_dynamic_properties( if (scope->ce_flags & ZEND_ACC_INTERFACE) { zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to interface"); } + if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) { + zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to readonly class %s", + ZSTR_VAL(scope->name) + ); + } scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 76470e811cea9..f444e4989088d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -797,6 +797,10 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */ zend_throw_exception(zend_ce_compile_error, "Multiple final modifiers are not allowed", 0); return 0; } + if ((flags & ZEND_ACC_READONLY_CLASS) && (new_flag & ZEND_ACC_READONLY_CLASS)) { + zend_throw_exception(zend_ce_compile_error, "Multiple readonly modifiers are not allowed", 0); + return 0; + } if ((new_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) && (new_flags & ZEND_ACC_FINAL)) { zend_throw_exception(zend_ce_compile_error, "Cannot use the final modifier on an abstract class", 0); @@ -6673,6 +6677,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 if (property_flags) { zend_op_array *op_array = CG(active_op_array); zend_class_entry *scope = op_array->scope; + bool is_ctor = scope && zend_is_constructor(op_array->function_name); if (!is_ctor) { @@ -6699,6 +6704,10 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 ZSTR_VAL(scope->name), ZSTR_VAL(name), ZSTR_VAL(str)); } + if (!(property_flags & ZEND_ACC_READONLY) && (scope->ce_flags & ZEND_ACC_READONLY_CLASS)) { + property_flags |= ZEND_ACC_READONLY; + } + /* Recompile the type, as it has different memory management requirements. */ zend_type type = ZEND_TYPE_INIT_NONE(0); if (type_ast) { @@ -7241,6 +7250,12 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f zend_error_noreturn(E_COMPILE_ERROR, "Properties cannot be declared abstract"); } + if ((ce->ce_flags & ZEND_ACC_READONLY_CLASS) && (flags & ZEND_ACC_STATIC)) { + zend_error_noreturn(E_COMPILE_ERROR, "Readonly class %s cannot declare static properties", + ZSTR_VAL(ce->name) + ); + } + for (i = 0; i < children; ++i) { zend_property_info *info; zend_ast *prop_ast = list->child[i]; @@ -7306,6 +7321,10 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f ZVAL_UNDEF(&value_zv); } + if ((ce->ce_flags & ZEND_ACC_READONLY_CLASS)) { + flags |= ZEND_ACC_READONLY; + } + if (flags & ZEND_ACC_READONLY) { if (!ZEND_TYPE_IS_SET(type)) { zend_error_noreturn(E_COMPILE_ERROR, "Readonly property %s::$%s must have type", diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 038eb6543f19e..72726bdab02af 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -240,7 +240,7 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Class Flags (unused: 16,21,30,31) | | | */ +/* Class Flags (unused: 21,30,31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -273,6 +273,9 @@ typedef struct _zend_oparray_context { /* without triggering a deprecation warning | | | */ #define ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES (1 << 15) /* X | | | */ /* | | | */ +/* Readonly class | | | */ +#define ZEND_ACC_READONLY_CLASS (1 << 16) /* X | | | */ +/* | | | */ /* Parent class is resolved (CE). | | | */ #define ZEND_ACC_RESOLVED_PARENT (1 << 17) /* X | | | */ /* | | | */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index c5660d258d88b..3921ee35fe0d6 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1430,6 +1430,13 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } } + if (UNEXPECTED((ce->ce_flags & ZEND_ACC_READONLY_CLASS) != (parent_ce->ce_flags & ZEND_ACC_READONLY_CLASS))) { + zend_error_noreturn(E_COMPILE_ERROR, "%s class %s cannot extend %s class %s", + ce->ce_flags & ZEND_ACC_READONLY_CLASS ? "Readonly" : "Non-readonly", ZSTR_VAL(ce->name), + parent_ce->ce_flags & ZEND_ACC_READONLY_CLASS ? "readonly" : "non-readonly", ZSTR_VAL(parent_ce->name) + ); + } + if (ce->parent_name) { zend_string_release_ex(ce->parent_name, 0); } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 01adacf065ef4..a333bf52a8dad 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -595,6 +595,7 @@ class_modifiers: class_modifier: T_ABSTRACT { $$ = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } | T_FINAL { $$ = ZEND_ACC_FINAL; } + | T_READONLY { $$ = ZEND_ACC_READONLY_CLASS|ZEND_ACC_NO_DYNAMIC_PROPERTIES; } ; trait_declaration_statement: diff --git a/build/gen_stub.php b/build/gen_stub.php index bd8166d3afe0d..b4daed524128d 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1797,6 +1797,10 @@ private function getFlagsAsString(): string $flags[] = "ZEND_ACC_ABSTRACT"; } + if ($this->flags & Class_::MODIFIER_READONLY) { + $flags[] = "ZEND_ACC_READONLY_CLASS"; + } + if ($this->isDeprecated) { $flags[] = "ZEND_ACC_DEPRECATED"; } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 5ca89f0d2a742..a901e454a9bcf 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -348,6 +348,9 @@ static void _class_string(smart_str *str, zend_class_entry *ce, zval *obj, char if (ce->ce_flags & ZEND_ACC_FINAL) { smart_str_append_printf(str, "final "); } + if (ce->ce_flags & ZEND_ACC_READONLY_CLASS) { + smart_str_append_printf(str, "readonly "); + } smart_str_append_printf(str, "class "); } smart_str_append_printf(str, "%s", ZSTR_VAL(ce->name)); @@ -4863,6 +4866,12 @@ ZEND_METHOD(ReflectionClass, isFinal) } /* }}} */ +/* Returns whether this class is readonly */ +ZEND_METHOD(ReflectionClass, isReadOnly) +{ + _class_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY_CLASS); +} + /* {{{ Returns whether this class is abstract */ ZEND_METHOD(ReflectionClass, isAbstract) { @@ -4875,8 +4884,7 @@ ZEND_METHOD(ReflectionClass, getModifiers) { reflection_object *intern; zend_class_entry *ce; - uint32_t keep_flags = ZEND_ACC_FINAL - | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; + uint32_t keep_flags = ZEND_ACC_FINAL | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS | ZEND_ACC_READONLY_CLASS; if (zend_parse_parameters_none() == FAILURE) { RETURN_THROWS(); @@ -7123,6 +7131,7 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_IMPLICIT_ABSTRACT", ZEND_ACC_IMPLICIT_ABSTRACT_CLASS); REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_EXPLICIT_ABSTRACT", ZEND_ACC_EXPLICIT_ABSTRACT_CLASS); REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_FINAL", ZEND_ACC_FINAL); + REGISTER_REFLECTION_CLASS_CONST_LONG(class, "IS_READONLY", ZEND_ACC_READONLY_CLASS); reflection_object_ptr = register_class_ReflectionObject(reflection_class_ptr); reflection_object_ptr->create_object = reflection_objects_new; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 22d01b6905168..33130d568c7b0 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -318,6 +318,8 @@ public function isAbstract(): bool {} /** @tentative-return-type */ public function isFinal(): bool {} + public function isReadOnly(): bool {} + /** @tentative-return-type */ public function getModifiers(): int {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 25a750f2bdcac..a2e598c2105be 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: c9656b23db965e890e73d0064005b981ee1991cf */ + * Stub hash: 4191864554b030bea40306c0d30090a8e2c76ab2 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -260,6 +260,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionClass_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionClass_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_isInstance, 0, 1, _IS_BOOL, 0) @@ -697,6 +699,7 @@ ZEND_METHOD(ReflectionClass, isTrait); ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); ZEND_METHOD(ReflectionClass, isFinal); +ZEND_METHOD(ReflectionClass, isReadOnly); ZEND_METHOD(ReflectionClass, getModifiers); ZEND_METHOD(ReflectionClass, isInstance); ZEND_METHOD(ReflectionClass, newInstance); @@ -963,6 +966,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { 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, isReadOnly, arginfo_class_ReflectionClass_isReadOnly, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getModifiers, arginfo_class_ReflectionClass_getModifiers, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isInstance, arginfo_class_ReflectionClass_isInstance, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newInstance, arginfo_class_ReflectionClass_newInstance, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/tests/ReflectionClass_modifiers_001.phpt b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt index cb2a6555f1956..6be2f155ff384 100644 --- a/ext/reflection/tests/ReflectionClass_modifiers_001.phpt +++ b/ext/reflection/tests/ReflectionClass_modifiers_001.phpt @@ -9,15 +9,17 @@ abstract class A {} class B extends A {} class C {} final class D {} +readonly class E {} interface I {} -$classes = array("A", "B", "C", "D", "I"); +$classes = array("A", "B", "C", "D", "E", "I"); foreach ($classes as $class) { $rc = new ReflectionClass($class); var_dump($rc->isFinal()); var_dump($rc->isInterface()); var_dump($rc->isAbstract()); + var_dump($rc->isReadOnly()); var_dump($rc->getModifiers()); } ?> @@ -25,20 +27,30 @@ foreach ($classes as $class) { bool(false) bool(false) bool(true) +bool(false) int(64) bool(false) bool(false) bool(false) +bool(false) int(0) bool(false) bool(false) bool(false) +bool(false) int(0) bool(true) bool(false) bool(false) +bool(false) int(32) bool(false) +bool(false) +bool(false) +bool(true) +int(65536) +bool(false) bool(true) bool(false) +bool(false) int(0) diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index 3ca0d9a37a82b..3b2c276ebc071 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -11,10 +11,11 @@ echo $rc; --EXPECT-- Class [ class ReflectionClass implements Stringable, Reflector ] { - - Constants [3] { + - Constants [4] { Constant [ public int IS_IMPLICIT_ABSTRACT ] { 16 } Constant [ public int IS_EXPLICIT_ABSTRACT ] { 64 } Constant [ public int IS_FINAL ] { 32 } + Constant [ public int IS_READONLY ] { 65536 } } - Static properties [0] { @@ -27,7 +28,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [55] { + - Methods [56] { Method [ private method __clone ] { - Parameters [0] { @@ -284,6 +285,13 @@ Class [ class ReflectionClass implements Stringable, Refle - Tentative return [ bool ] } + Method [ public method isReadOnly ] { + + - Parameters [0] { + } + - Return [ bool ] + } + Method [ public method getModifiers ] { - Parameters [0] { diff --git a/ext/reflection/tests/ReflectionClass_toString_005.phpt b/ext/reflection/tests/ReflectionClass_toString_005.phpt new file mode 100644 index 0000000000000..4d4178828cc8e --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_toString_005.phpt @@ -0,0 +1,34 @@ +--TEST-- +Using ReflectionClass::__toString() on readonly classes +--FILE-- + +--EXPECTF-- +Class [ readonly class Foo ] { + @@ %s 3-6 + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [2] { + Property [ public readonly int $bar ] + Property [ public readonly int $baz ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/readonly_class.phpt b/ext/reflection/tests/readonly_class.phpt new file mode 100644 index 0000000000000..dd685b900c634 --- /dev/null +++ b/ext/reflection/tests/readonly_class.phpt @@ -0,0 +1,25 @@ +--TEST-- +Readonly class reflection +--FILE-- +isReadOnly()); +var_dump(($foo->getModifiers() & ReflectionClass::IS_READONLY) != 0); + +$bar = new ReflectionClass(Bar::class); +var_dump($bar->isReadOnly()); +var_dump(($bar->getModifiers() & ReflectionClass::IS_READONLY) != 0); + +?> +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true)