mirror of
https://github.com/php/php-src.git
synced 2026-03-24 08:12:21 +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:
@@ -134,6 +134,8 @@ PHP 8.6 UPGRADE NOTES
|
||||
|
||||
- Reflection:
|
||||
. ReflectionConstant::inNamespace()
|
||||
. ReflectionProperty::isReadable() ReflectionProperty::isWritable() were
|
||||
added.
|
||||
|
||||
- Standard:
|
||||
. `clamp()` returns the given value if in range, else return the nearest
|
||||
|
||||
@@ -6601,6 +6601,242 @@ 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;
|
||||
}
|
||||
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 */
|
||||
ZEND_METHOD(ReflectionExtension, __construct)
|
||||
{
|
||||
|
||||
@@ -574,6 +574,10 @@ class ReflectionProperty implements Reflector
|
||||
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 */
|
||||
|
||||
13
ext/reflection/php_reflection_arginfo.h
generated
13
ext/reflection/php_reflection_arginfo.h
generated
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit php_reflection.stub.php instead.
|
||||
* Stub hash: b09497083efa7035dab6047f6d845ceaec81579e
|
||||
* Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41
|
||||
* Has decl header: yes */
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
@@ -894,6 +901,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);
|
||||
@@ -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, 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
|
||||
};
|
||||
|
||||
|
||||
8
ext/reflection/php_reflection_decl.h
generated
8
ext/reflection/php_reflection_decl.h
generated
@@ -1,12 +1,12 @@
|
||||
/* This is a generated file, edit php_reflection.stub.php instead.
|
||||
* Stub hash: b09497083efa7035dab6047f6d845ceaec81579e */
|
||||
* Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41 */
|
||||
|
||||
#ifndef ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H
|
||||
#define ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H
|
||||
#ifndef ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H
|
||||
#define ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H
|
||||
|
||||
typedef enum zend_enum_PropertyHookType {
|
||||
ZEND_ENUM_PropertyHookType_Get = 1,
|
||||
ZEND_ENUM_PropertyHookType_Set = 2,
|
||||
} zend_enum_PropertyHookType;
|
||||
|
||||
#endif /* ZEND_PHP_REFLECTION_DECL_b09497083efa7035dab6047f6d845ceaec81579e_H */
|
||||
#endif /* ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H */
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
72
ext/reflection/tests/ReflectionProperty_isReadable_init.phpt
Normal file
72
ext/reflection/tests/ReflectionProperty_isReadable_init.phpt
Normal 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)
|
||||
@@ -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
|
||||
29
ext/reflection/tests/ReflectionProperty_isReadable_lazy.phpt
Normal file
29
ext/reflection/tests/ReflectionProperty_isReadable_lazy.phpt
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
29
ext/reflection/tests/ReflectionProperty_isWritable_lazy.phpt
Normal file
29
ext/reflection/tests/ReflectionProperty_isWritable_lazy.phpt
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user