Skip to content

Commit 47d23c6

Browse files
committed
Implement ReflectionProperty::is{Readable,Writable}()
Fixes GH-15309 Fixes GH-16175
1 parent 7c31e5f commit 47d23c6

File tree

5 files changed

+333
-1
lines changed

5 files changed

+333
-1
lines changed

ext/reflection/php_reflection.c

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6617,6 +6617,147 @@ ZEND_METHOD(ReflectionProperty, isFinal)
66176617
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL);
66186618
}
66196619

6620+
static zend_result get_ce_from_scope_name(zend_class_entry **scope, zend_string *scope_name, zend_execute_data *execute_data)
6621+
{
6622+
if (!scope_name) {
6623+
*scope = NULL;
6624+
return SUCCESS;
6625+
}
6626+
if (zend_string_equals(scope_name, ZSTR_KNOWN(ZEND_STR_STATIC))) {
6627+
*scope = EX(prev_execute_data)->func->common.scope;
6628+
return SUCCESS;
6629+
}
6630+
6631+
*scope = zend_lookup_class(scope_name);
6632+
if (!*scope) {
6633+
zend_throw_error(NULL, "Class \"%s\" not found", ZSTR_VAL(scope_name));
6634+
return FAILURE;
6635+
}
6636+
return SUCCESS;
6637+
}
6638+
6639+
static zend_always_inline uint32_t set_visibility_to_visibility(uint32_t set_visibility)
6640+
{
6641+
switch (set_visibility) {
6642+
case ZEND_ACC_PUBLIC_SET:
6643+
return ZEND_ACC_PUBLIC;
6644+
case ZEND_ACC_PROTECTED_SET:
6645+
return ZEND_ACC_PROTECTED;
6646+
case ZEND_ACC_PRIVATE_SET:
6647+
return ZEND_ACC_PRIVATE;
6648+
EMPTY_SWITCH_DEFAULT_CASE();
6649+
}
6650+
}
6651+
6652+
static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_class_entry *scope)
6653+
{
6654+
if (!(visibility & ZEND_ACC_PUBLIC) && (scope != ce)) {
6655+
if (!scope) {
6656+
return false;
6657+
}
6658+
if (visibility & ZEND_ACC_PRIVATE) {
6659+
return false;
6660+
}
6661+
ZEND_ASSERT(visibility & ZEND_ACC_PROTECTED);
6662+
if (!instanceof_function(scope, ce) && !instanceof_function(ce, scope)) {
6663+
return false;
6664+
}
6665+
}
6666+
return true;
6667+
}
6668+
6669+
ZEND_METHOD(ReflectionProperty, isReadable)
6670+
{
6671+
reflection_object *intern;
6672+
property_reference *ref;
6673+
zend_string *scope_name = ZSTR_KNOWN(ZEND_STR_STATIC);
6674+
6675+
ZEND_PARSE_PARAMETERS_START(0, 1)
6676+
Z_PARAM_OPTIONAL
6677+
Z_PARAM_STR_OR_NULL(scope_name)
6678+
ZEND_PARSE_PARAMETERS_END();
6679+
6680+
GET_REFLECTION_OBJECT_PTR(ref);
6681+
zend_property_info *prop = ref->prop;
6682+
if (!prop) {
6683+
_DO_THROW("May not use isReadable on dynamic properties");
6684+
RETURN_THROWS();
6685+
}
6686+
6687+
zend_class_entry *scope;
6688+
if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) {
6689+
RETURN_THROWS();
6690+
}
6691+
6692+
if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) {
6693+
RETURN_FALSE;
6694+
}
6695+
6696+
if (prop->flags & ZEND_ACC_VIRTUAL) {
6697+
ZEND_ASSERT(prop->hooks);
6698+
if (!prop->hooks[ZEND_PROPERTY_HOOK_GET]) {
6699+
RETURN_FALSE;
6700+
}
6701+
}
6702+
6703+
RETURN_TRUE;
6704+
}
6705+
6706+
ZEND_METHOD(ReflectionProperty, isWritable)
6707+
{
6708+
reflection_object *intern;
6709+
property_reference *ref;
6710+
zend_object *obj;
6711+
zend_string *scope_name = ZSTR_KNOWN(ZEND_STR_STATIC);
6712+
6713+
ZEND_PARSE_PARAMETERS_START(1, 2)
6714+
Z_PARAM_OBJ(obj)
6715+
Z_PARAM_OPTIONAL
6716+
Z_PARAM_STR_OR_NULL(scope_name)
6717+
ZEND_PARSE_PARAMETERS_END();
6718+
6719+
GET_REFLECTION_OBJECT_PTR(ref);
6720+
zend_property_info *prop = ref->prop;
6721+
if (!prop) {
6722+
_DO_THROW("May not use isReadable on dynamic properties");
6723+
RETURN_THROWS();
6724+
}
6725+
6726+
zend_class_entry *scope;
6727+
if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) {
6728+
RETURN_THROWS();
6729+
}
6730+
if (!instanceof_function(obj->ce, prop->ce)) {
6731+
_DO_THROW("Given object is not an instance of the class this property was declared in");
6732+
RETURN_THROWS();
6733+
}
6734+
6735+
uint32_t set_visibility = prop->flags & ZEND_ACC_PPP_SET_MASK;
6736+
if (!set_visibility) {
6737+
set_visibility = zend_visibility_to_set_visibility(prop->flags & ZEND_ACC_PPP_MASK);
6738+
}
6739+
if (!check_visibility(set_visibility_to_visibility(set_visibility), prop->ce, scope)) {
6740+
RETURN_FALSE;
6741+
}
6742+
6743+
if (prop->flags & ZEND_ACC_VIRTUAL) {
6744+
ZEND_ASSERT(prop->hooks);
6745+
if (!prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
6746+
RETURN_FALSE;
6747+
}
6748+
}
6749+
6750+
if (prop->flags & ZEND_ACC_READONLY) {
6751+
ZEND_ASSERT(prop->offset != ZEND_VIRTUAL_PROPERTY_OFFSET);
6752+
zval *prop_val = OBJ_PROP(obj, prop->offset);
6753+
if (Z_TYPE_P(prop_val) != IS_UNDEF && !(Z_PROP_FLAG_P(prop_val) & IS_PROP_REINITABLE)) {
6754+
RETURN_FALSE;
6755+
}
6756+
}
6757+
6758+
RETURN_TRUE;
6759+
}
6760+
66206761
/* {{{ Constructor. Throws an Exception in case the given extension does not exist */
66216762
ZEND_METHOD(ReflectionExtension, __construct)
66226763
{

ext/reflection/php_reflection.stub.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,10 @@ public function hasHook(PropertyHookType $type): bool {}
569569
public function getHook(PropertyHookType $type): ?ReflectionMethod {}
570570

571571
public function isFinal(): bool {}
572+
573+
public function isReadable(?string $scope = 'static'): bool {}
574+
575+
public function isWritable(object $object, ?string $scope = 'static'): bool {}
572576
}
573577

574578
/** @not-serializable */

ext/reflection/php_reflection_arginfo.h

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
--TEST--
2+
Test ReflectionProperty::isReadable()
3+
--FILE--
4+
<?php
5+
6+
class A {}
7+
8+
class B extends A {
9+
public $a;
10+
protected $b;
11+
private $c;
12+
public protected(set) int $d;
13+
public $e { get => 42; }
14+
public $f { set {} }
15+
}
16+
17+
class C extends B {}
18+
19+
$test = static function ($scope) {
20+
$rc = new ReflectionClass(B::class);
21+
foreach ($rc->getProperties() as $rp) {
22+
echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': ';
23+
var_dump($rp->isReadable($scope));
24+
}
25+
};
26+
27+
foreach (['A', 'B', 'C'] as $scope) {
28+
$test($scope);
29+
$test->bindTo(null, $scope)('static');
30+
}
31+
32+
$test(null);
33+
$test->bindTo(null, null)('static');
34+
35+
?>
36+
--EXPECT--
37+
a from A: bool(true)
38+
b from A: bool(true)
39+
c from A: bool(false)
40+
d from A: bool(true)
41+
e from A: bool(true)
42+
f from A: bool(false)
43+
a from static: bool(true)
44+
b from static: bool(true)
45+
c from static: bool(false)
46+
d from static: bool(true)
47+
e from static: bool(true)
48+
f from static: bool(false)
49+
a from B: bool(true)
50+
b from B: bool(true)
51+
c from B: bool(true)
52+
d from B: bool(true)
53+
e from B: bool(true)
54+
f from B: bool(false)
55+
a from static: bool(true)
56+
b from static: bool(true)
57+
c from static: bool(true)
58+
d from static: bool(true)
59+
e from static: bool(true)
60+
f from static: bool(false)
61+
a from C: bool(true)
62+
b from C: bool(true)
63+
c from C: bool(false)
64+
d from C: bool(true)
65+
e from C: bool(true)
66+
f from C: bool(false)
67+
a from static: bool(true)
68+
b from static: bool(true)
69+
c from static: bool(false)
70+
d from static: bool(true)
71+
e from static: bool(true)
72+
f from static: bool(false)
73+
a from global: bool(true)
74+
b from global: bool(false)
75+
c from global: bool(false)
76+
d from global: bool(true)
77+
e from global: bool(true)
78+
f from global: bool(false)
79+
a from static: bool(true)
80+
b from static: bool(false)
81+
c from static: bool(false)
82+
d from static: bool(true)
83+
e from static: bool(true)
84+
f from static: bool(false)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
Test ReflectionProperty::isWritable()
3+
--FILE--
4+
<?php
5+
6+
class A {
7+
public $a;
8+
protected $b;
9+
private $c;
10+
public protected(set) int $d;
11+
public $e { get => 42; }
12+
public $f { set {} }
13+
public readonly int $g;
14+
public private(set) int $h;
15+
16+
public function setG($g) {
17+
$this->g = $g;
18+
}
19+
20+
public function __clone() {
21+
$rp = new ReflectionProperty(A::class, 'g');
22+
echo $rp->getName() . ' from static (initialized, clone): ';
23+
var_dump($rp->isWritable($this));
24+
}
25+
}
26+
27+
$test = static function ($scope) {
28+
$rc = new ReflectionClass(A::class);
29+
foreach ($rc->getProperties() as $rp) {
30+
echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': ';
31+
var_dump($rp->isWritable(new A(), $scope));
32+
33+
if ($rp->name == 'g') {
34+
$a = new A();
35+
$a->setG(42);
36+
echo $rp->getName() . ' from ' . ($scope ?? 'global') . ' (initialized): ';
37+
var_dump($rp->isWritable($a, $scope));
38+
clone $a;
39+
}
40+
}
41+
};
42+
43+
$test('A');
44+
$test->bindTo(null, 'A')('static');
45+
46+
$test(null);
47+
$test->bindTo(null, null)('static');
48+
49+
?>
50+
--EXPECT--
51+
a from A: bool(true)
52+
b from A: bool(true)
53+
c from A: bool(true)
54+
d from A: bool(true)
55+
e from A: bool(false)
56+
f from A: bool(true)
57+
g from A: bool(true)
58+
g from A (initialized): bool(false)
59+
g from static (initialized, clone): bool(true)
60+
h from A: bool(true)
61+
a from static: bool(true)
62+
b from static: bool(true)
63+
c from static: bool(true)
64+
d from static: bool(true)
65+
e from static: bool(false)
66+
f from static: bool(true)
67+
g from static: bool(true)
68+
g from static (initialized): bool(false)
69+
g from static (initialized, clone): bool(true)
70+
h from static: bool(true)
71+
a from global: bool(true)
72+
b from global: bool(false)
73+
c from global: bool(false)
74+
d from global: bool(false)
75+
e from global: bool(false)
76+
f from global: bool(true)
77+
g from global: bool(false)
78+
g from global (initialized): bool(false)
79+
g from static (initialized, clone): bool(true)
80+
h from global: bool(false)
81+
a from static: bool(true)
82+
b from static: bool(false)
83+
c from static: bool(false)
84+
d from static: bool(false)
85+
e from static: bool(false)
86+
f from static: bool(true)
87+
g from static: bool(false)
88+
g from static (initialized): bool(false)
89+
g from static (initialized, clone): bool(true)
90+
h from static: bool(false)

0 commit comments

Comments
 (0)