Skip to content

Commit cad43f4

Browse files
committed
Make ReflectionProperty and ReflectionMethod access private/protected symbols by default
With this patch, it is no longer required to call `ReflectionProperty#setAccessible()` or `ReflectionMethod#setAccessible()` with `true`. If a userland consumer already got to the point of accessing object/class information via reflection, it makes little sense for `ext/reflection` to disallow accessing `private`/`protected` symbols by default. After this patch, calling `ReflectionProperty#setAccessible(true)` or `ReflectionMethod#setAccessible(true)` on newly instantiated `ReflectionProperty` or `ReflectionMethod` respectively will have no effect. Full RFC text for the voted upon https://wiki.php.net/rfc/make-reflection-setaccessible-no-op is as follows: ``` ====== PHP RFC: Make reflection setAccessible() no-op ====== * Version: 1.0 * Date: 2021-06-13 * Author: Marco Pivetta * Status: Accepted * First Published at: https://wiki.php.net/rfc/make-reflection-setaccessible-no-op ===== Introduction ===== The `ext-reflection` API is designed to inspect static details of a code-base, as well as reading and manipulating runtime state and calling internal details of objects that are otherwise inaccessible. These methods are most notably: * `ReflectionMethod#invoke(): mixed` * `ReflectionMethod#invokeArgs(mixed ...$args): mixed` * `ReflectionProperty#getValue(object $object): mixed` * `ReflectionProperty#setValue(object $object, mixed $value): void` While breaking encapsulation principles that allow for safe coding practices, these methods are extremely valuable to tools like: * mappers * serializers * debuggers * etc. Infrastructural instrumentation is often required to do things that are in direct conflict with encapsulation itself. The 4 methods listed above change behavior depending on the only mutable state within the scope of `ext-reflection` classes, which is an "accessible" flag. This "accessibility" flag is steered by: * `ReflectionMethod#setAccessible(bool $accessible): void` * `ReflectionProperty#setAccessible(bool $accessible): void` Attempting to use any of the above listed methods without configuring accessibility first will lead to an exception being thrown. For example: <code php> class Foo { private $bar = 'a'; } (new ReflectionProperty(Foo::class, 'bar'))->getValue(); </code> https://3v4l.org/ousrD : <code> Fatal error: Uncaught ReflectionException: Cannot access non-public property Foo::$bar in <SNIP> </code> ==== The problem with mutability ==== By having `ReflectionProperty#setAccessible()` and `ReflectionMethod#setAccessible()`, any consumer of a `ReflectionMethod` or `ReflectionProperty` that is given by a third party must ensure that `#setAccessible()` is called: <code php> function doSomethingWithState(MyObject $o, ReflectionProperty $p) : void { $p->setAccessible(true); // wasteful safety check doSomethingWith($p->getValue($o)); } </code> In addition to that, any developer that is intentionally using the reflection API (after having evaluated its trade-off) will have to use this obnoxious syntax in order to use it at its fullest: <code php> $p = new ReflectionProperty(MyClass::class, 'propertyName'); $p->setAccessible(true); // now $p is usable </code> ===== Proposal ===== This RFC proposes to: * make `ReflectionProperty` and `ReflectionMethod` behave as if `#setAccessible(true)` had been called upfront * make `ReflectionProperty#setAccessible()` and `ReflectionMethod#setAccessible()` no-op operations, with no side-effects nor state mutation involved After the RFC is successfully accepted/implemented, the following code should no longer throw, improving therefore the ergonomics around reflection. <code php> class Foo { private $bar = 'a'; } (new ReflectionProperty(Foo::class, 'bar'))->getValue(); </code> ==== Deprecations ==== In order to ease migration to PHP 8.1, and minimize runtime side-effects, a deprecation is explicitly avoided in this RFC. Instead, a deprecation should be introduced when a new/separate RFC plans for the removal of `ReflectionProperty#setAccessible()` and `ReflectionMethod#setAccessible()`. Such RFC will be raised **after** the release of PHP 8.1, if this RFC is accepted. ===== Backward Incompatible Changes ===== Although of minimal concern, it is true that some behavior will change: * `ReflectionProperty#getValue()` will no longer throw an exception when used against a protected/private property * `ReflectionProperty#setValue()` will no longer throw an exception when used against a protected/private property * `ReflectionMethod#invoke()` will no longer throw an exception when used against a protected/private method * `ReflectionMethod#invokeArgs()` will no longer throw an exception when used against a protected/private method * for extensions developers, `reflection_object->ignore_visibility` no longer exists ===== Proposed PHP Version(s) ===== 8.1 ===== RFC Impact ===== ==== To SAPIs ==== None ==== To Existing Extensions ==== None ==== To Opcache ==== None ==== New Constants ==== None ==== php.ini Defaults ==== None ===== Open Issues ===== None ===== Proposed Voting Choices ===== Accept turning `ReflectionProperty#setAccessible()` and `ReflectionMethod#setAccessible()` into a no-op? (yes/no) ===== Patches and Tests ===== #5412 ===== Vote ===== This is a Yes/No vote, requiring a 2/3 majority. Voting started on 2021-06-23 and ends on 2021-07-07. <doodle title="Make reflection setAccessible() no-op" auth="ocramius" voteType="single" closed="true"> * Yes * No </doodle> ```
1 parent bc39abe commit cad43f4

18 files changed

+116
-274
lines changed

Zend/tests/bug63762.phpt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ Bug #63762 - Sigsegv when Exception::$trace is changed by user
55
$e = new Exception();
66

77
$ref = new ReflectionProperty($e, 'trace');
8-
$ref->setAccessible(TRUE);
98

109
echo "Array of NULL:\n";
1110
$ref->setValue($e, array(NULL));

Zend/tests/bug72177.phpt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ class Parnt
2121
$this->child = new Child();
2222

2323
$prop = new \ReflectionProperty($this, 'child');
24-
$prop->setAccessible(true);
2524
$prop->setValue($this, null);
2625
}
2726
}

Zend/tests/bug72177_2.phpt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class Bar extends Foo
2727

2828
$r = new ReflectionProperty(Foo::class, 'bar');
2929

30-
$r->setAccessible(true);
3130
echo "OK\n";
3231
?>
3332
--EXPECT--

Zend/tests/bug78921.phpt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class OtherClass
2727

2828
$reflectionClass = new ReflectionClass('OtherClass');
2929
$reflectionProperty = $reflectionClass->getProperty('prop');
30-
$reflectionProperty->setAccessible(true);
3130
$value = $reflectionProperty->getValue();
3231
echo "Value is $value\n";
3332

ext/reflection/php_reflection.c

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ typedef struct {
171171
void *ptr;
172172
zend_class_entry *ce;
173173
reflection_type_t ref_type;
174-
unsigned int ignore_visibility:1;
175174
zend_object zo;
176175
} reflection_object;
177176

@@ -1440,7 +1439,6 @@ static void reflection_property_factory(zend_class_entry *ce, zend_string *name,
14401439
intern->ptr = reference;
14411440
intern->ref_type = REF_TYPE_PROPERTY;
14421441
intern->ce = ce;
1443-
intern->ignore_visibility = 0;
14441442
ZVAL_STR_COPY(reflection_prop_name(object), name);
14451443
ZVAL_STR_COPY(reflection_prop_class(object), prop ? prop->ce->name : ce->name);
14461444
}
@@ -1463,7 +1461,6 @@ static void reflection_class_constant_factory(zend_string *name_str, zend_class_
14631461
intern->ptr = constant;
14641462
intern->ref_type = REF_TYPE_CLASS_CONSTANT;
14651463
intern->ce = constant->ce;
1466-
intern->ignore_visibility = 0;
14671464

14681465
ZVAL_STR_COPY(reflection_prop_name(object), name_str);
14691466
ZVAL_STR_COPY(reflection_prop_class(object), constant->ce->name);
@@ -1482,7 +1479,6 @@ static void reflection_enum_case_factory(zend_class_entry *ce, zend_string *name
14821479
intern->ptr = constant;
14831480
intern->ref_type = REF_TYPE_CLASS_CONSTANT;
14841481
intern->ce = constant->ce;
1485-
intern->ignore_visibility = 0;
14861482

14871483
ZVAL_STR_COPY(reflection_prop_name(object), name_str);
14881484
ZVAL_STR_COPY(reflection_prop_class(object), constant->ce->name);
@@ -3313,15 +3309,6 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
33133309
RETURN_THROWS();
33143310
}
33153311

3316-
if (!(mptr->common.fn_flags & ZEND_ACC_PUBLIC) && intern->ignore_visibility == 0) {
3317-
zend_throw_exception_ex(reflection_exception_ptr, 0,
3318-
"Trying to invoke %s method %s::%s() from scope %s",
3319-
mptr->common.fn_flags & ZEND_ACC_PROTECTED ? "protected" : "private",
3320-
ZSTR_VAL(mptr->common.scope->name), ZSTR_VAL(mptr->common.function_name),
3321-
ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name));
3322-
RETURN_THROWS();
3323-
}
3324-
33253312
if (variadic) {
33263313
ZEND_PARSE_PARAMETERS_START(1, -1)
33273314
Z_PARAM_OBJECT_OR_NULL(object)
@@ -3696,16 +3683,11 @@ ZEND_METHOD(ReflectionMethod, getPrototype)
36963683
/* {{{ Sets whether non-public methods can be invoked */
36973684
ZEND_METHOD(ReflectionMethod, setAccessible)
36983685
{
3699-
reflection_object *intern;
37003686
bool visible;
37013687

37023688
if (zend_parse_parameters(ZEND_NUM_ARGS(), "b", &visible) == FAILURE) {
37033689
RETURN_THROWS();
37043690
}
3705-
3706-
intern = Z_REFLECTION_P(ZEND_THIS);
3707-
3708-
intern->ignore_visibility = visible;
37093691
}
37103692
/* }}} */
37113693

@@ -3745,7 +3727,6 @@ ZEND_METHOD(ReflectionClassConstant, __construct)
37453727
intern->ptr = constant;
37463728
intern->ref_type = REF_TYPE_CLASS_CONSTANT;
37473729
intern->ce = constant->ce;
3748-
intern->ignore_visibility = 0;
37493730
ZVAL_STR_COPY(reflection_prop_name(object), constname);
37503731
ZVAL_STR_COPY(reflection_prop_class(object), constant->ce->name);
37513732
}
@@ -5453,7 +5434,6 @@ ZEND_METHOD(ReflectionProperty, __construct)
54535434
intern->ptr = reference;
54545435
intern->ref_type = REF_TYPE_PROPERTY;
54555436
intern->ce = ce;
5456-
intern->ignore_visibility = 0;
54575437
}
54585438
/* }}} */
54595439

@@ -5580,13 +5560,6 @@ ZEND_METHOD(ReflectionProperty, getValue)
55805560

55815561
GET_REFLECTION_OBJECT_PTR(ref);
55825562

5583-
if (!(prop_get_flags(ref) & ZEND_ACC_PUBLIC) && intern->ignore_visibility == 0) {
5584-
zend_throw_exception_ex(reflection_exception_ptr, 0,
5585-
"Cannot access non-public property %s::$%s",
5586-
ZSTR_VAL(intern->ce->name), ZSTR_VAL(ref->unmangled_name));
5587-
RETURN_THROWS();
5588-
}
5589-
55905563
if (prop_get_flags(ref) & ZEND_ACC_STATIC) {
55915564
member_p = zend_read_static_property_ex(intern->ce, ref->unmangled_name, 0);
55925565
if (member_p) {
@@ -5630,13 +5603,6 @@ ZEND_METHOD(ReflectionProperty, setValue)
56305603

56315604
GET_REFLECTION_OBJECT_PTR(ref);
56325605

5633-
if (!(prop_get_flags(ref) & ZEND_ACC_PUBLIC) && intern->ignore_visibility == 0) {
5634-
zend_throw_exception_ex(reflection_exception_ptr, 0,
5635-
"Cannot access non-public property %s::$%s",
5636-
ZSTR_VAL(intern->ce->name), ZSTR_VAL(ref->unmangled_name));
5637-
RETURN_THROWS();
5638-
}
5639-
56405606
if (prop_get_flags(ref) & ZEND_ACC_STATIC) {
56415607
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "z", &value) == FAILURE) {
56425608
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &tmp, &value) == FAILURE) {
@@ -5669,13 +5635,6 @@ ZEND_METHOD(ReflectionProperty, isInitialized)
56695635

56705636
GET_REFLECTION_OBJECT_PTR(ref);
56715637

5672-
if (!(prop_get_flags(ref) & ZEND_ACC_PUBLIC) && intern->ignore_visibility == 0) {
5673-
zend_throw_exception_ex(reflection_exception_ptr, 0,
5674-
"Cannot access non-public property %s::$%s",
5675-
ZSTR_VAL(intern->ce->name), ZSTR_VAL(ref->unmangled_name));
5676-
RETURN_THROWS();
5677-
}
5678-
56795638
if (prop_get_flags(ref) & ZEND_ACC_STATIC) {
56805639
member_p = zend_read_static_property_ex(intern->ce, ref->unmangled_name, 1);
56815640
if (member_p) {
@@ -5762,16 +5721,11 @@ ZEND_METHOD(ReflectionProperty, getAttributes)
57625721
/* {{{ Sets whether non-public properties can be requested */
57635722
ZEND_METHOD(ReflectionProperty, setAccessible)
57645723
{
5765-
reflection_object *intern;
57665724
bool visible;
57675725

57685726
if (zend_parse_parameters(ZEND_NUM_ARGS(), "b", &visible) == FAILURE) {
57695727
RETURN_THROWS();
57705728
}
5771-
5772-
intern = Z_REFLECTION_P(ZEND_THIS);
5773-
5774-
intern->ignore_visibility = visible;
57755729
}
57765730
/* }}} */
57775731

ext/reflection/tests/ReflectionClass_getProperty_003.phpt

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,9 @@ function showInfo($name) {
4949
echo $e->getMessage() . "\n";
5050
return;
5151
}
52-
try {
53-
var_dump($rp);
54-
var_dump($rp->getValue($myC));
55-
} catch (Exception $e) {
56-
echo $e->getMessage() . "\n";
57-
return;
58-
}
52+
53+
var_dump($rp);
54+
var_dump($rp->getValue($myC));
5955
}
6056

6157

@@ -110,7 +106,7 @@ object(ReflectionProperty)#%d (2) {
110106
["class"]=>
111107
string(1) "A"
112108
}
113-
Cannot access non-public property C::$protA
109+
string(10) "protA in A"
114110
--- (Reflecting on privA) ---
115111
Property C::$privA does not exist
116112
--- (Reflecting on pubB) ---
@@ -128,7 +124,7 @@ object(ReflectionProperty)#%d (2) {
128124
["class"]=>
129125
string(1) "B"
130126
}
131-
Cannot access non-public property C::$protB
127+
string(10) "protB in B"
132128
--- (Reflecting on privB) ---
133129
Property C::$privB does not exist
134130
--- (Reflecting on pubC) ---
@@ -146,15 +142,15 @@ object(ReflectionProperty)#%d (2) {
146142
["class"]=>
147143
string(1) "C"
148144
}
149-
Cannot access non-public property C::$protC
145+
string(10) "protC in C"
150146
--- (Reflecting on privC) ---
151147
object(ReflectionProperty)#%d (2) {
152148
["name"]=>
153149
string(5) "privC"
154150
["class"]=>
155151
string(1) "C"
156152
}
157-
Cannot access non-public property C::$privC
153+
string(10) "privC in C"
158154
--- (Reflecting on doesNotExist) ---
159155
Property C::$doesNotExist does not exist
160156
--- (Reflecting on A::pubC) ---
@@ -172,15 +168,15 @@ object(ReflectionProperty)#%d (2) {
172168
["class"]=>
173169
string(1) "A"
174170
}
175-
Cannot access non-public property A::$protC
171+
string(10) "protC in A"
176172
--- (Reflecting on A::privC) ---
177173
object(ReflectionProperty)#%d (2) {
178174
["name"]=>
179175
string(5) "privC"
180176
["class"]=>
181177
string(1) "A"
182178
}
183-
Cannot access non-public property A::$privC
179+
string(10) "privC in A"
184180
--- (Reflecting on B::pubC) ---
185181
object(ReflectionProperty)#%d (2) {
186182
["name"]=>
@@ -196,15 +192,15 @@ object(ReflectionProperty)#%d (2) {
196192
["class"]=>
197193
string(1) "B"
198194
}
199-
Cannot access non-public property B::$protC
195+
string(10) "protC in B"
200196
--- (Reflecting on B::privC) ---
201197
object(ReflectionProperty)#%d (2) {
202198
["name"]=>
203199
string(5) "privC"
204200
["class"]=>
205201
string(1) "B"
206202
}
207-
Cannot access non-public property B::$privC
203+
string(10) "privC in B"
208204
--- (Reflecting on c::pubC) ---
209205
object(ReflectionProperty)#%d (2) {
210206
["name"]=>
@@ -230,15 +226,15 @@ object(ReflectionProperty)#%d (2) {
230226
["class"]=>
231227
string(1) "C"
232228
}
233-
Cannot access non-public property C::$protC
229+
string(10) "protC in C"
234230
--- (Reflecting on C::privC) ---
235231
object(ReflectionProperty)#%d (2) {
236232
["name"]=>
237233
string(5) "privC"
238234
["class"]=>
239235
string(1) "C"
240236
}
241-
Cannot access non-public property C::$privC
237+
string(10) "privC in C"
242238
--- (Reflecting on X::pubC) ---
243239
Fully qualified property name X::$pubC does not specify a base class of C
244240
--- (Reflecting on X::protC) ---

ext/reflection/tests/ReflectionClass_getProperty_004.phpt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ object(ReflectionProperty)#%d (2) {
110110
["class"]=>
111111
string(1) "A"
112112
}
113-
Cannot access non-public property C::$protA
113+
string(10) "protA in A"
114114
--- (Reflecting on privA) ---
115115
Property C::$privA does not exist
116116
--- (Reflecting on pubB) ---
@@ -128,7 +128,7 @@ object(ReflectionProperty)#%d (2) {
128128
["class"]=>
129129
string(1) "B"
130130
}
131-
Cannot access non-public property C::$protB
131+
string(10) "protB in B"
132132
--- (Reflecting on privB) ---
133133
Property C::$privB does not exist
134134
--- (Reflecting on pubC) ---
@@ -146,15 +146,15 @@ object(ReflectionProperty)#%d (2) {
146146
["class"]=>
147147
string(1) "C"
148148
}
149-
Cannot access non-public property C::$protC
149+
string(10) "protC in C"
150150
--- (Reflecting on privC) ---
151151
object(ReflectionProperty)#%d (2) {
152152
["name"]=>
153153
string(5) "privC"
154154
["class"]=>
155155
string(1) "C"
156156
}
157-
Cannot access non-public property C::$privC
157+
string(10) "privC in C"
158158
--- (Reflecting on doesNotExist) ---
159159
Property C::$doesNotExist does not exist
160160
--- (Reflecting on A::pubC) ---
@@ -172,15 +172,15 @@ object(ReflectionProperty)#%d (2) {
172172
["class"]=>
173173
string(1) "A"
174174
}
175-
Cannot access non-public property A::$protC
175+
string(10) "protC in C"
176176
--- (Reflecting on A::privC) ---
177177
object(ReflectionProperty)#%d (2) {
178178
["name"]=>
179179
string(5) "privC"
180180
["class"]=>
181181
string(1) "A"
182182
}
183-
Cannot access non-public property A::$privC
183+
string(10) "privC in A"
184184
--- (Reflecting on B::pubC) ---
185185
object(ReflectionProperty)#%d (2) {
186186
["name"]=>
@@ -196,15 +196,15 @@ object(ReflectionProperty)#%d (2) {
196196
["class"]=>
197197
string(1) "B"
198198
}
199-
Cannot access non-public property B::$protC
199+
string(10) "protC in C"
200200
--- (Reflecting on B::privC) ---
201201
object(ReflectionProperty)#%d (2) {
202202
["name"]=>
203203
string(5) "privC"
204204
["class"]=>
205205
string(1) "B"
206206
}
207-
Cannot access non-public property B::$privC
207+
string(10) "privC in B"
208208
--- (Reflecting on c::pubC) ---
209209
object(ReflectionProperty)#%d (2) {
210210
["name"]=>
@@ -230,15 +230,15 @@ object(ReflectionProperty)#%d (2) {
230230
["class"]=>
231231
string(1) "C"
232232
}
233-
Cannot access non-public property C::$protC
233+
string(10) "protC in C"
234234
--- (Reflecting on C::privC) ---
235235
object(ReflectionProperty)#%d (2) {
236236
["name"]=>
237237
string(5) "privC"
238238
["class"]=>
239239
string(1) "C"
240240
}
241-
Cannot access non-public property C::$privC
241+
string(10) "privC in C"
242242
--- (Reflecting on X::pubC) ---
243243
Fully qualified property name X::$pubC does not specify a base class of C
244244
--- (Reflecting on X::protC) ---

ext/reflection/tests/ReflectionMethod_invokeArgs_error3.phpt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,7 @@ echo "\nStatic method:\n";
4949
var_dump($staticMethod->invokeArgs(null, array()));
5050

5151
echo "\nPrivate method:\n";
52-
try {
53-
var_dump($privateMethod->invokeArgs($testClassInstance, array()));
54-
} catch (ReflectionException $e) {
55-
var_dump($e->getMessage());
56-
}
52+
var_dump($privateMethod->invokeArgs($testClassInstance, array()));
5753

5854
echo "\nAbstract method:\n";
5955
$abstractMethod = new ReflectionMethod("AbstractClass::foo");
@@ -79,7 +75,8 @@ Exception: Using $this when not in object context
7975
NULL
8076

8177
Private method:
82-
string(86) "Trying to invoke private method TestClass::privateMethod() from scope ReflectionMethod"
78+
Called privateMethod()
79+
NULL
8380

8481
Abstract method:
8582
string(53) "Trying to invoke abstract method AbstractClass::foo()"

0 commit comments

Comments
 (0)