Skip to content

Commit 15c28c2

Browse files
committed
Implement ReflectionProperty::is{Readable,Writable}()
Fixes GH-15309 Fixes GH-16175
1 parent c27368c commit 15c28c2

File tree

5 files changed

+352
-1
lines changed

5 files changed

+352
-1
lines changed

ext/reflection/php_reflection.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6633,6 +6633,168 @@ ZEND_METHOD(ReflectionProperty, isFinal)
66336633
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL);
66346634
}
66356635

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

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(?object $object = null, ?string $scope = 'static'): bool {}
576+
577+
public function isWritable(?object $object = null, ?string $scope = 'static'): bool {}
574578
}
575579

576580
/** @not-serializable */

ext/reflection/php_reflection_arginfo.h

Lines changed: 12 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(null, $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)