1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Implement ReflectionProperty::is{Readable,Writable}()

RFC: https://wiki.php.net/rfc/isreadable-iswriteable

Fixes GH-15309
Fixes GH-16175
Closes GH-16209
This commit is contained in:
Ilija Tovilo
2024-10-04 00:59:24 +02:00
parent 7923dc22f9
commit e4f727d61e
23 changed files with 942 additions and 5 deletions

View File

@@ -134,6 +134,8 @@ PHP 8.6 UPGRADE NOTES
- Reflection: - Reflection:
. ReflectionConstant::inNamespace() . ReflectionConstant::inNamespace()
. ReflectionProperty::isReadable() ReflectionProperty::isWritable() were
added.
- Standard: - Standard:
. `clamp()` returns the given value if in range, else return the nearest . `clamp()` returns the given value if in range, else return the nearest

View File

@@ -6601,6 +6601,242 @@ ZEND_METHOD(ReflectionProperty, isFinal)
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL); _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;
}
if (obj && zend_lazy_object_must_init(obj)) {
obj = zend_lazy_object_init(obj);
if (!obj) {
RETURN_THROWS();
}
if (obj->properties && zend_hash_find_ptr(obj->properties, ref->unmangled_name)) {
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])) {
retry_declared:;
zval *prop_val = OBJ_PROP(obj, prop->offset);
if (Z_TYPE_P(prop_val) == IS_UNDEF) {
if (zend_lazy_object_must_init(obj) && (Z_PROP_FLAG_P(prop_val) & IS_PROP_LAZY)) {
obj = zend_lazy_object_init(obj);
if (!obj) {
RETURN_THROWS();
}
goto retry_declared;
}
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)) {
retry:;
zval *prop_val = OBJ_PROP(obj, prop->offset);
if (Z_TYPE_P(prop_val) == IS_UNDEF
&& zend_lazy_object_must_init(obj)
&& (Z_PROP_FLAG_P(prop_val) & IS_PROP_LAZY)) {
obj = zend_lazy_object_init(obj);
if (!obj) {
RETURN_THROWS();
}
goto retry;
}
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 */ /* {{{ Constructor. Throws an Exception in case the given extension does not exist */
ZEND_METHOD(ReflectionExtension, __construct) ZEND_METHOD(ReflectionExtension, __construct)
{ {

View File

@@ -574,6 +574,10 @@ class ReflectionProperty implements Reflector
public function getHook(PropertyHookType $type): ?ReflectionMethod {} public function getHook(PropertyHookType $type): ?ReflectionMethod {}
public function isFinal(): bool {} public function isFinal(): bool {}
public function isReadable(?string $scope, ?object $object = null): bool {}
public function isWritable(?string $scope, ?object $object = null): bool {}
} }
/** @not-serializable */ /** @not-serializable */

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit php_reflection.stub.php instead. /* This is a generated file, edit php_reflection.stub.php instead.
* Stub hash: b09497083efa7035dab6047f6d845ceaec81579e * Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41
* Has decl header: yes */ * Has decl header: yes */
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0)
@@ -473,6 +473,13 @@ ZEND_END_ARG_INFO()
#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType #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 #define arginfo_class_ReflectionClassConstant___clone arginfo_class_ReflectionFunctionAbstract___clone
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2) ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionClassConstant___construct, 0, 0, 2)
@@ -894,6 +901,8 @@ ZEND_METHOD(ReflectionProperty, getHooks);
ZEND_METHOD(ReflectionProperty, hasHook); ZEND_METHOD(ReflectionProperty, hasHook);
ZEND_METHOD(ReflectionProperty, getHook); ZEND_METHOD(ReflectionProperty, getHook);
ZEND_METHOD(ReflectionProperty, isFinal); ZEND_METHOD(ReflectionProperty, isFinal);
ZEND_METHOD(ReflectionProperty, isReadable);
ZEND_METHOD(ReflectionProperty, isWritable);
ZEND_METHOD(ReflectionClassConstant, __construct); ZEND_METHOD(ReflectionClassConstant, __construct);
ZEND_METHOD(ReflectionClassConstant, __toString); ZEND_METHOD(ReflectionClassConstant, __toString);
ZEND_METHOD(ReflectionClassConstant, getName); ZEND_METHOD(ReflectionClassConstant, getName);
@@ -1198,6 +1207,8 @@ static const zend_function_entry class_ReflectionProperty_methods[] = {
ZEND_ME(ReflectionProperty, hasHook, arginfo_class_ReflectionProperty_hasHook, ZEND_ACC_PUBLIC) 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, getHook, arginfo_class_ReflectionProperty_getHook, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, isFinal, arginfo_class_ReflectionProperty_isFinal, 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 ZEND_FE_END
}; };

View File

@@ -1,12 +1,12 @@
/* This is a generated file, edit php_reflection.stub.php instead. /* This is a generated file, edit php_reflection.stub.php instead.
* Stub hash: b09497083efa7035dab6047f6d845ceaec81579e */ * Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41 */
#ifndef ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H #ifndef ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H
#define ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H #define ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H
typedef enum zend_enum_PropertyHookType { typedef enum zend_enum_PropertyHookType {
ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Get = 1,
ZEND_ENUM_PropertyHookType_Set = 2, ZEND_ENUM_PropertyHookType_Set = 2,
} zend_enum_PropertyHookType; } zend_enum_PropertyHookType;
#endif /* ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H */ #endif /* ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H */

View File

@@ -0,0 +1,28 @@
--TEST--
Test ReflectionProperty::isReadable() dynamic
--FILE--
<?php
#[AllowDynamicProperties]
class A {}
$a = new A;
$a->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)

View File

@@ -0,0 +1,39 @@
--TEST--
Test ReflectionProperty::isReadable() hooks
--FILE--
<?php
class A {
public $a { get => $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)

View File

@@ -0,0 +1,72 @@
--TEST--
Test ReflectionProperty::isReadable() init
--FILE--
<?php
class A {
public $a;
public int $b;
public int $c = 42;
public int $d;
public int $e;
public function __construct() {
unset($this->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)

View File

@@ -0,0 +1,20 @@
--TEST--
Test ReflectionProperty::isReadable() invalid scope
--FILE--
<?php
class A {
public $prop;
}
$r = new ReflectionProperty('A', 'prop');
try {
$r->isReadable('B', null);
} catch (Error $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
?>
--EXPECT--
Error: Class "B" not found

View File

@@ -0,0 +1,29 @@
--TEST--
Test ReflectionProperty::isReadable() lazy
--CREDITS--
Arnaud Le Blanc (arnaud-lb)
--FILE--
<?php
class A {
public int $a;
public int $b;
public function __construct() {
$this->a = 1;
}
}
$rc = new ReflectionClass(A::class);
$obj = $rc->newLazyProxy(fn() => new A());
$rp = new ReflectionProperty(A::class, 'a');
var_dump($rp->isReadable(null, $obj));
$rp = new ReflectionProperty(A::class, 'b');
var_dump($rp->isReadable(null, $obj));
?>
--EXPECT--
bool(true)
bool(false)

View File

@@ -0,0 +1,25 @@
--TEST--
Test ReflectionProperty::isReadable() lazy dynamic
--CREDITS--
Arnaud Le Blanc (arnaud-lb)
--FILE--
<?php
#[AllowDynamicProperties]
class A {
public $_;
public function __construct() {
$this->prop = 1;
}
}
$rc = new ReflectionClass(A::class);
$obj = $rc->newLazyProxy(fn() => new A());
$rp = new ReflectionProperty(new A, 'prop');
var_dump($rp->isReadable(null, $obj));
?>
--EXPECT--
bool(true)

View File

@@ -0,0 +1,33 @@
--TEST--
Test ReflectionProperty::isReadable() lazy isset
--CREDITS--
Arnaud Le Blanc (arnaud-lb)
--FILE--
<?php
#[AllowDynamicProperties]
class A {
public $_;
public function __construct() {
$this->prop = 1;
}
public function __isset($name) {
return false;
}
public function __get($name) {
return null;
}
}
$rc = new ReflectionClass(A::class);
$obj = $rc->newLazyProxy(fn() => new A);
$rp = new ReflectionProperty(new A, 'prop');
var_dump($rp->isReadable(null, $obj));
?>
--EXPECT--
bool(false)

View File

@@ -0,0 +1,36 @@
--TEST--
Test ReflectionProperty::isReadable() static
--FILE--
<?php
class A {
public static $a;
public static int $b;
public static int $c = 42;
protected static int $d = 42;
private static int $e = 42;
public private(set) static int $f = 42;
}
$r = new ReflectionProperty('A', 'a');
try {
$r->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)

View File

@@ -0,0 +1,36 @@
--TEST--
Test ReflectionProperty::isReadable() unrelated object
--FILE--
<?php
class A {
}
class B extends A {
public $prop;
}
class C extends B {}
class D {}
function test($obj) {
$r = new ReflectionProperty('B', 'prop');
try {
var_dump($r->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

View File

@@ -0,0 +1,67 @@
--TEST--
Test ReflectionProperty::isReadable() visibility
--FILE--
<?php
class A {}
class B extends A {
public $a;
protected $b;
private $c;
public protected(set) int $d = 42;
}
class C extends B {}
class D extends C {
public function __get($name) {}
}
class E extends D {
private $f;
public function __isset($name) {
return $name === 'f';
}
}
function test($scope) {
$rc = new ReflectionClass(B::class);
foreach ($rc->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)

View File

@@ -0,0 +1,32 @@
--TEST--
Test ReflectionProperty::isWritable() dynamic
--FILE--
<?php
#[AllowDynamicProperties]
class A {
private $a;
}
$a = new A;
$r = new ReflectionProperty($a, 'a');
var_dump($r->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)

View File

@@ -0,0 +1,39 @@
--TEST--
Test ReflectionProperty::isWritable() visibility
--FILE--
<?php
class A {
public $a { get => $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)

View File

@@ -0,0 +1,20 @@
--TEST--
Test ReflectionProperty::isWritable() invalid scope
--FILE--
<?php
class A {
public $prop;
}
$r = new ReflectionProperty('A', 'prop');
try {
$r->isWritable('B', null);
} catch (Error $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
?>
--EXPECT--
Error: Class "B" not found

View File

@@ -0,0 +1,29 @@
--TEST--
Test ReflectionProperty::isWritable() lazy
--CREDITS--
Arnaud Le Blanc (arnaud-lb)
--FILE--
<?php
class A {
public public(set) readonly int $a;
public public(set) readonly int $b;
public function __construct() {
$this->a = 1;
}
}
$rc = new ReflectionClass(A::class);
$obj = $rc->newLazyProxy(fn() => new A());
$rp = new ReflectionProperty(A::class, 'a');
var_dump($rp->isWritable(null, $obj));
$rp = new ReflectionProperty(A::class, 'b');
var_dump($rp->isWritable(null, $obj));
?>
--EXPECT--
bool(false)
bool(true)

View File

@@ -0,0 +1,39 @@
--TEST--
Test ReflectionProperty::isWritable() readonly
--FILE--
<?php
class A {
public readonly int $a;
public readonly int $b;
public function __construct() {
$this->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)

View File

@@ -0,0 +1,36 @@
--TEST--
Test ReflectionProperty::isWritable() static
--FILE--
<?php
class A {
public static $a;
public static int $b;
public static int $c = 42;
protected static int $d = 42;
private static int $e = 42;
public private(set) static int $f = 42;
}
$r = new ReflectionProperty('A', 'a');
try {
$r->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)

View File

@@ -0,0 +1,36 @@
--TEST--
Test ReflectionProperty::isWritable() unrelated object
--FILE--
<?php
class A {
}
class B extends A {
public $prop;
}
class C extends B {}
class D {}
function test($obj) {
$r = new ReflectionProperty('B', 'prop');
try {
var_dump($r->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

View File

@@ -0,0 +1,68 @@
--TEST--
Test ReflectionProperty::isWritable() visibility
--FILE--
<?php
class A {}
class B extends A {
public $a;
protected $b;
private $c;
public private(set) int $d;
public protected(set) int $e;
public readonly int $f;
}
class C extends B {}
class D extends C {
public function __set($name, $value) {}
}
function test($scope) {
$rc = new ReflectionClass(B::class);
foreach ($rc->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)