diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index d6e55c982b42..16ecfb76073b 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6633,6 +6633,215 @@ ZEND_METHOD(ReflectionProperty, isFinal) _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL); } +static zend_result get_ce_from_scope_name(zend_class_entry **scope, zend_string *scope_name, zend_execute_data *execute_data) +{ + if (!scope_name) { + *scope = NULL; + return SUCCESS; + } + + *scope = zend_lookup_class(scope_name); + if (!*scope) { + zend_throw_error(NULL, "Class \"%s\" not found", ZSTR_VAL(scope_name)); + return FAILURE; + } + return SUCCESS; +} + +static zend_always_inline uint32_t set_visibility_to_visibility(uint32_t set_visibility) +{ + switch (set_visibility) { + case ZEND_ACC_PUBLIC_SET: + return ZEND_ACC_PUBLIC; + case ZEND_ACC_PROTECTED_SET: + return ZEND_ACC_PROTECTED; + case ZEND_ACC_PRIVATE_SET: + return ZEND_ACC_PRIVATE; + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + +static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_class_entry *scope) +{ + if (!(visibility & ZEND_ACC_PUBLIC) && (scope != ce)) { + if (!scope) { + return false; + } + if (visibility & ZEND_ACC_PRIVATE) { + return false; + } + ZEND_ASSERT(visibility & ZEND_ACC_PROTECTED); + if (!instanceof_function(scope, ce) && !instanceof_function(ce, scope)) { + return false; + } + } + return true; +} + +ZEND_METHOD(ReflectionProperty, isReadable) +{ + reflection_object *intern; + property_reference *ref; + zend_string *scope_name; + zend_object *obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR_OR_NULL(scope_name) + Z_PARAM_OPTIONAL + Z_PARAM_OBJ_OR_NULL(obj) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_property_info *prop = ref->prop; + if (prop && obj) { + if (prop->flags & ZEND_ACC_STATIC) { + _DO_THROW("null is expected as object argument for static properties"); + RETURN_THROWS(); + } + if (!instanceof_function(obj->ce, prop->ce)) { + _DO_THROW("Given object is not an instance of the class this property was declared in"); + RETURN_THROWS(); + } + prop = reflection_property_get_effective_prop(ref, intern->ce, obj); + } + + zend_class_entry *ce = obj ? obj->ce : intern->ce; + if (!prop) { + if (obj && obj->properties && zend_hash_find_ptr(obj->properties, ref->unmangled_name)) { + RETURN_TRUE; + } +handle_magic_get: + if (ce->__get) { + if (obj && ce->__isset) { + uint32_t *guard = zend_get_property_guard(obj, ref->unmangled_name); + if (!((*guard) & ZEND_GUARD_PROPERTY_ISSET)) { + GC_ADDREF(obj); + *guard |= ZEND_GUARD_PROPERTY_ISSET; + zval member; + ZVAL_STR(&member, ref->unmangled_name); + zend_call_known_instance_method_with_1_params(ce->__isset, obj, return_value, &member); + *guard &= ~ZEND_GUARD_PROPERTY_ISSET; + OBJ_RELEASE(obj); + return; + } + } + RETURN_TRUE; + } + RETURN_FALSE; + } + + zend_class_entry *scope; + if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) { + RETURN_THROWS(); + } + + if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) { + if (!(prop->flags & ZEND_ACC_STATIC)) { + goto handle_magic_get; + } + RETURN_FALSE; + } + + if (prop->flags & ZEND_ACC_VIRTUAL) { + ZEND_ASSERT(prop->hooks); + if (!prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + RETURN_FALSE; + } + } else if (obj && (!prop->hooks || !prop->hooks[ZEND_PROPERTY_HOOK_GET])) { + zval *prop_val = OBJ_PROP(obj, prop->offset); + if (Z_TYPE_P(prop_val) == IS_UNDEF) { + if (!(Z_PROP_FLAG_P(prop_val) & IS_PROP_UNINIT)) { + goto handle_magic_get; + } + RETURN_FALSE; + } + } else if (prop->flags & ZEND_ACC_STATIC) { + if (ce->default_static_members_count && !CE_STATIC_MEMBERS(ce)) { + zend_class_init_statics(ce); + } + zval *prop_val = CE_STATIC_MEMBERS(ce) + prop->offset; + RETURN_BOOL(!Z_ISUNDEF_P(prop_val)); + } + + RETURN_TRUE; +} + +ZEND_METHOD(ReflectionProperty, isWritable) +{ + reflection_object *intern; + property_reference *ref; + zend_string *scope_name; + zend_object *obj = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR_OR_NULL(scope_name) + Z_PARAM_OPTIONAL + Z_PARAM_OBJ_OR_NULL(obj) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(ref); + + zend_property_info *prop = ref->prop; + if (prop && obj) { + if (prop->flags & ZEND_ACC_STATIC) { + _DO_THROW("null is expected as object argument for static properties"); + RETURN_THROWS(); + } + if (!instanceof_function(obj->ce, prop->ce)) { + _DO_THROW("Given object is not an instance of the class this property was declared in"); + RETURN_THROWS(); + } + prop = reflection_property_get_effective_prop(ref, intern->ce, obj); + } + + zend_class_entry *ce = obj ? obj->ce : intern->ce; + if (!prop) { + if (!(ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) { + RETURN_TRUE; + } + /* This path is effectively unreachable, but theoretically possible for + * two internal classes where ZEND_ACC_NO_DYNAMIC_PROPERTIES is only + * added to the subclass, in which case a ReflectionProperty can be + * constructed on the parent class, and then tested on the subclass. */ +handle_magic_set: + RETURN_BOOL(ce->__set); + } + + zend_class_entry *scope; + if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) { + RETURN_THROWS(); + } + + if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) { + if (!(prop->flags & ZEND_ACC_STATIC)) { + goto handle_magic_set; + } + RETURN_FALSE; + } + uint32_t set_visibility = prop->flags & ZEND_ACC_PPP_SET_MASK; + if (!set_visibility) { + set_visibility = zend_visibility_to_set_visibility(prop->flags & ZEND_ACC_PPP_MASK); + } + if (!check_visibility(set_visibility_to_visibility(set_visibility), prop->ce, scope)) { + RETURN_FALSE; + } + + if (prop->flags & ZEND_ACC_VIRTUAL) { + ZEND_ASSERT(prop->hooks); + if (!prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + RETURN_FALSE; + } + } else if (obj && (prop->flags & ZEND_ACC_READONLY)) { + zval *prop_val = OBJ_PROP(obj, prop->offset); + if (Z_TYPE_P(prop_val) != IS_UNDEF && !(Z_PROP_FLAG_P(prop_val) & IS_PROP_REINITABLE)) { + RETURN_FALSE; + } + } + + RETURN_TRUE; +} + /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ ZEND_METHOD(ReflectionExtension, __construct) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 91c70d6ffdb1..a4b97d280d8e 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -571,6 +571,10 @@ public function hasHook(PropertyHookType $type): bool {} public function getHook(PropertyHookType $type): ?ReflectionMethod {} public function isFinal(): bool {} + + public function isReadable(?string $scope, ?object $object = null): bool {} + + public function isWritable(?string $scope, ?object $object = null): bool {} } /** @not-serializable */ diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 62275423b3ca..aca80acd6a8e 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: fd645a0b0db39d94ca25b39ffe64d7f05bad6bea */ + * Stub hash: 918c9d5ef5f69a060848768d7a455577d3bb5796 */ 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) @@ -472,6 +472,13 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isReadable, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, scope, IS_STRING, 1) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, object, IS_OBJECT, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_ReflectionProperty_isWritable arginfo_class_ReflectionProperty_isReadable + #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2) @@ -891,6 +898,8 @@ ZEND_METHOD(ReflectionProperty, getHooks); ZEND_METHOD(ReflectionProperty, hasHook); ZEND_METHOD(ReflectionProperty, getHook); ZEND_METHOD(ReflectionProperty, isFinal); +ZEND_METHOD(ReflectionProperty, isReadable); +ZEND_METHOD(ReflectionProperty, isWritable); ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, getName); @@ -1194,6 +1203,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, hasHook, arginfo_class_ReflectionProperty_hasHook, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, getHook, arginfo_class_ReflectionProperty_getHook, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isFinal, arginfo_class_ReflectionProperty_isFinal, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isReadable, arginfo_class_ReflectionProperty_isReadable, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isWritable, arginfo_class_ReflectionProperty_isWritable, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt new file mode 100644 index 000000000000..a4056a60555e --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt @@ -0,0 +1,28 @@ +--TEST-- +Test ReflectionProperty::isReadable() dynamic +--FILE-- +a = 'a'; +$r = new ReflectionProperty($a, 'a'); + +var_dump($r->isReadable(null, $a)); +unset($a->a); +var_dump($r->isReadable(null, $a)); + +$a = new A; +var_dump($r->isReadable(null, $a)); + +var_dump($r->isReadable(null, null)); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt new file mode 100644 index 000000000000..5fc31b2163eb --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ReflectionProperty::isReadable() hooks +--FILE-- + $this->a; } + public $b { get => 42; } + public $c { set => $value; } + public $d { set {} } + public $e { get => $this->e; set => $value; } + public $f { get {} set {} } +} + +function test($scope) { + $rc = new ReflectionClass(A::class); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isReadable($scope, null)); + } +} + +test('A'); +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(true) +c from A: bool(true) +d from A: bool(false) +e from A: bool(true) +f from A: bool(true) +a from global: bool(true) +b from global: bool(true) +c from global: bool(true) +d from global: bool(false) +e from global: bool(true) +f from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_init.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_init.phpt new file mode 100644 index 000000000000..242f28b57a82 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_init.phpt @@ -0,0 +1,72 @@ +--TEST-- +Test ReflectionProperty::isReadable() init +--FILE-- +e); + } +} + +class B { + public int $f; + public int $g; + public int $h; + + public function __construct() { + unset($this->g); + unset($this->h); + } + + public function __isset($name) { + return $name === 'h'; + } + + public function __get($name) {} +} + +class C { + public int $i; + public int $j; + public int $k; + + public function __construct() { + unset($this->j); + unset($this->k); + } + + public function __get($name) {} +} + +function test($class) { + $rc = new ReflectionClass($class); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from global: '; + var_dump($rp->isReadable(null, new $class)); + } +} + +test('A'); +test('B'); +test('C'); + +?> +--EXPECT-- +a from global: bool(true) +b from global: bool(false) +c from global: bool(true) +d from global: bool(false) +e from global: bool(false) +f from global: bool(false) +g from global: bool(false) +h from global: bool(true) +i from global: bool(false) +j from global: bool(true) +k from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt new file mode 100644 index 000000000000..ae9f446c6771 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test ReflectionProperty::isReadable() invalid scope +--FILE-- +isReadable('B', null); +} catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Class "B" not found diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_static.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_static.phpt new file mode 100644 index 000000000000..e927f428a1f8 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_static.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isReadable() static +--FILE-- +isReadable(null, new A); +} catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +$rc = new ReflectionClass('A'); +foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from global: '; + var_dump($rp->isReadable(null)); +} + +?> +--EXPECT-- +ReflectionException: null is expected as object argument for static properties +a from global: bool(true) +b from global: bool(false) +c from global: bool(true) +d from global: bool(false) +e from global: bool(false) +f from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt new file mode 100644 index 000000000000..ce48958c28ce --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_unrelated_object.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isReadable() unrelated object +--FILE-- +isReadable(null, $obj)); + } catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} + +test(new A); +test(new B); +test(new C); +test(new D); + +?> +--EXPECT-- +ReflectionException: Given object is not an instance of the class this property was declared in +bool(true) +bool(true) +ReflectionException: Given object is not an instance of the class this property was declared in diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt new file mode 100644 index 000000000000..205ff85293ff --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_visibility.phpt @@ -0,0 +1,67 @@ +--TEST-- +Test ReflectionProperty::isReadable() visibility +--FILE-- +getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isReadable($scope, $scope && $scope !== 'A' ? new $scope : null)); + } +} + +foreach (['A', 'B', 'C', 'D', 'E'] as $scope) { + test($scope); +} +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(true) +c from A: bool(false) +d from A: bool(true) +a from B: bool(true) +b from B: bool(true) +c from B: bool(true) +d from B: bool(true) +a from C: bool(true) +b from C: bool(true) +c from C: bool(false) +d from C: bool(true) +a from D: bool(true) +b from D: bool(true) +c from D: bool(true) +d from D: bool(true) +a from E: bool(true) +b from E: bool(true) +c from E: bool(false) +d from E: bool(true) +a from global: bool(true) +b from global: bool(false) +c from global: bool(false) +d from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt new file mode 100644 index 000000000000..e9da6675ddae --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_dynamic.phpt @@ -0,0 +1,32 @@ +--TEST-- +Test ReflectionProperty::isWritable() dynamic +--FILE-- +isWritable(null, $a)); +var_dump($r->isWritable(null, null)); + +$a->b = 'b'; +$r = new ReflectionProperty($a, 'b'); +var_dump($r->isWritable(null, $a)); + +$a = new A; +var_dump($r->isWritable(null, $a)); + +var_dump($r->isWritable(null, null)); + +?> +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt new file mode 100644 index 000000000000..2617ec65b9e7 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_hooks.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ReflectionProperty::isWritable() visibility +--FILE-- + $this->a; } + public $b { get => 42; } + public $c { set => $value; } + public $d { set {} } + public $e { get => $this->e; set => $value; } + public $f { get {} set {} } +} + +function test($scope) { + $rc = new ReflectionClass(A::class); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isWritable($scope, null)); + } +} + +test('A'); +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(false) +c from A: bool(true) +d from A: bool(true) +e from A: bool(true) +f from A: bool(true) +a from global: bool(true) +b from global: bool(false) +c from global: bool(true) +d from global: bool(true) +e from global: bool(true) +f from global: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt new file mode 100644 index 000000000000..7474e521b35e --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test ReflectionProperty::isWritable() invalid scope +--FILE-- +isWritable('B', null); +} catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Class "B" not found diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt new file mode 100644 index 000000000000..256f14895908 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_readonly.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test ReflectionProperty::isWritable() readonly +--FILE-- +a = 42; + } + + public function __clone() { + test($this); + $this->a = 43; + test($this); + } +} + +function test($instance) { + $rc = new ReflectionClass($instance); + foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from A: '; + var_dump($rp->isWritable($instance::class, $instance)); + } +} + +test(new A); +clone new A; + +?> +--EXPECT-- +a from A: bool(false) +b from A: bool(true) +a from A: bool(true) +b from A: bool(true) +a from A: bool(false) +b from A: bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_static.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_static.phpt new file mode 100644 index 000000000000..008d585c3153 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_static.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isWritable() static +--FILE-- +isWritable(null, new A); +} catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +$rc = new ReflectionClass('A'); +foreach ($rc->getProperties() as $rp) { + echo $rp->getName() . ' from global: '; + var_dump($rp->isWritable(null)); +} + +?> +--EXPECT-- +ReflectionException: null is expected as object argument for static properties +a from global: bool(true) +b from global: bool(true) +c from global: bool(true) +d from global: bool(false) +e from global: bool(false) +f from global: bool(false) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt new file mode 100644 index 000000000000..3e1e6a394c8f --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_unrelated_object.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test ReflectionProperty::isWritable() unrelated object +--FILE-- +isWritable(null, $obj)); + } catch (Exception $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} + +test(new A); +test(new B); +test(new C); +test(new D); + +?> +--EXPECT-- +ReflectionException: Given object is not an instance of the class this property was declared in +bool(true) +bool(true) +ReflectionException: Given object is not an instance of the class this property was declared in diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt new file mode 100644 index 000000000000..1585f4cba531 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_visibility.phpt @@ -0,0 +1,68 @@ +--TEST-- +Test ReflectionProperty::isWritable() visibility +--FILE-- +getProperties() as $rp) { + echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': '; + var_dump($rp->isWritable($scope, $scope && $scope !== 'A' ? new $scope : null)); + } +} + +test('A'); +test('B'); +test('C'); +test('D'); +test(null); + +?> +--EXPECT-- +a from A: bool(true) +b from A: bool(true) +c from A: bool(false) +d from A: bool(false) +e from A: bool(true) +f from A: bool(true) +a from B: bool(true) +b from B: bool(true) +c from B: bool(true) +d from B: bool(true) +e from B: bool(true) +f from B: bool(true) +a from C: bool(true) +b from C: bool(true) +c from C: bool(false) +d from C: bool(false) +e from C: bool(true) +f from C: bool(true) +a from D: bool(true) +b from D: bool(true) +c from D: bool(true) +d from D: bool(false) +e from D: bool(true) +f from D: bool(true) +a from global: bool(true) +b from global: bool(false) +c from global: bool(false) +d from global: bool(false) +e from global: bool(false) +f from global: bool(false)