Skip to content

Commit 8503fa7

Browse files
committed
Implement ReflectionProperty::is{Readable,Writable}()
Fixes GH-15309 Fixes GH-16175
1 parent a4bb5b9 commit 8503fa7

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
@@ -6656,6 +6656,147 @@ ZEND_METHOD(ReflectionProperty, isFinal)
66566656
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL);
66576657
}
66586658

6659+
static zend_result get_ce_from_scope_name(zend_class_entry **scope, zend_string *scope_name, zend_execute_data *execute_data)
6660+
{
6661+
if (!scope_name) {
6662+
*scope = NULL;
6663+
return SUCCESS;
6664+
}
6665+
if (zend_string_equals(scope_name, ZSTR_KNOWN(ZEND_STR_STATIC))) {
6666+
*scope = EX(prev_execute_data)->func->common.scope;
6667+
return SUCCESS;
6668+
}
6669+
6670+
*scope = zend_lookup_class(scope_name);
6671+
if (!*scope) {
6672+
zend_throw_error(NULL, "Class \"%s\" not found", ZSTR_VAL(scope_name));
6673+
return FAILURE;
6674+
}
6675+
return SUCCESS;
6676+
}
6677+
6678+
static zend_always_inline uint32_t set_visibility_to_visibility(uint32_t set_visibility)
6679+
{
6680+
switch (set_visibility) {
6681+
case ZEND_ACC_PUBLIC_SET:
6682+
return ZEND_ACC_PUBLIC;
6683+
case ZEND_ACC_PROTECTED_SET:
6684+
return ZEND_ACC_PROTECTED;
6685+
case ZEND_ACC_PRIVATE_SET:
6686+
return ZEND_ACC_PRIVATE;
6687+
EMPTY_SWITCH_DEFAULT_CASE();
6688+
}
6689+
}
6690+
6691+
static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_class_entry *scope)
6692+
{
6693+
if (!(visibility & ZEND_ACC_PUBLIC) && (scope != ce)) {
6694+
if (!scope) {
6695+
return false;
6696+
}
6697+
if (visibility & ZEND_ACC_PRIVATE) {
6698+
return false;
6699+
}
6700+
ZEND_ASSERT(visibility & ZEND_ACC_PROTECTED);
6701+
if (!instanceof_function(scope, ce) && !instanceof_function(ce, scope)) {
6702+
return false;
6703+
}
6704+
}
6705+
return true;
6706+
}
6707+
6708+
ZEND_METHOD(ReflectionProperty, isReadable)
6709+
{
6710+
reflection_object *intern;
6711+
property_reference *ref;
6712+
zend_string *scope_name = ZSTR_KNOWN(ZEND_STR_STATIC);
6713+
6714+
ZEND_PARSE_PARAMETERS_START(0, 1)
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+
6731+
if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) {
6732+
RETURN_FALSE;
6733+
}
6734+
6735+
if (prop->flags & ZEND_ACC_VIRTUAL) {
6736+
ZEND_ASSERT(prop->hooks);
6737+
if (!prop->hooks[ZEND_PROPERTY_HOOK_GET]) {
6738+
RETURN_FALSE;
6739+
}
6740+
}
6741+
6742+
RETURN_TRUE;
6743+
}
6744+
6745+
ZEND_METHOD(ReflectionProperty, isWritable)
6746+
{
6747+
reflection_object *intern;
6748+
property_reference *ref;
6749+
zend_object *obj;
6750+
zend_string *scope_name = ZSTR_KNOWN(ZEND_STR_STATIC);
6751+
6752+
ZEND_PARSE_PARAMETERS_START(1, 2)
6753+
Z_PARAM_OBJ(obj)
6754+
Z_PARAM_OPTIONAL
6755+
Z_PARAM_STR_OR_NULL(scope_name)
6756+
ZEND_PARSE_PARAMETERS_END();
6757+
6758+
GET_REFLECTION_OBJECT_PTR(ref);
6759+
zend_property_info *prop = ref->prop;
6760+
if (!prop) {
6761+
_DO_THROW("May not use isReadable on dynamic properties");
6762+
RETURN_THROWS();
6763+
}
6764+
6765+
zend_class_entry *scope;
6766+
if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) {
6767+
RETURN_THROWS();
6768+
}
6769+
if (!instanceof_function(obj->ce, prop->ce)) {
6770+
_DO_THROW("Given object is not an instance of the class this property was declared in");
6771+
RETURN_THROWS();
6772+
}
6773+
6774+
uint32_t set_visibility = prop->flags & ZEND_ACC_PPP_SET_MASK;
6775+
if (!set_visibility) {
6776+
set_visibility = zend_visibility_to_set_visibility(prop->flags & ZEND_ACC_PPP_MASK);
6777+
}
6778+
if (!check_visibility(set_visibility_to_visibility(set_visibility), prop->ce, scope)) {
6779+
RETURN_FALSE;
6780+
}
6781+
6782+
if (prop->flags & ZEND_ACC_VIRTUAL) {
6783+
ZEND_ASSERT(prop->hooks);
6784+
if (!prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
6785+
RETURN_FALSE;
6786+
}
6787+
}
6788+
6789+
if (prop->flags & ZEND_ACC_READONLY) {
6790+
ZEND_ASSERT(prop->offset != ZEND_VIRTUAL_PROPERTY_OFFSET);
6791+
zval *prop_val = OBJ_PROP(obj, prop->offset);
6792+
if (Z_TYPE_P(prop_val) != IS_UNDEF && !(Z_PROP_FLAG_P(prop_val) & IS_PROP_REINITABLE)) {
6793+
RETURN_FALSE;
6794+
}
6795+
}
6796+
6797+
RETURN_TRUE;
6798+
}
6799+
66596800
/* {{{ Constructor. Throws an Exception in case the given extension does not exist */
66606801
ZEND_METHOD(ReflectionExtension, __construct)
66616802
{

ext/reflection/php_reflection.stub.php

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

573573
public function isFinal(): bool {}
574+
575+
public function isReadable(?string $scope = 'static'): bool {}
576+
577+
public function isWritable(object $object, ?string $scope = 'static'): bool {}
574578
}
575579

576580
/** @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)