PHP RFC: Make reflection setAccessible() no-op
- Version: 1.0
- Date: 2021-06-13
- Author: Marco Pivetta
- Status: Implemented (in PHP 8.1)
- 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:
class Foo { private $bar = 'a'; } (new ReflectionProperty(Foo::class, 'bar'))->getValue();
Fatal error: Uncaught ReflectionException: Cannot access non-public property Foo::$bar in <SNIP>
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:
function doSomethingWithState(MyObject $o, ReflectionProperty $p) : void { $p->setAccessible(true); // wasteful safety check doSomethingWith($p->getValue($o)); }
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:
$p = new ReflectionProperty(MyClass::class, 'propertyName'); $p->setAccessible(true); // now $p is usable
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.
class Foo { private $bar = 'a'; } (new ReflectionProperty(Foo::class, 'bar'))->getValue();
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
Vote
This is a Yes/No vote, requiring a 2/3 majority. Voting started on 2021-06-23 and ends on 2021-07-07.