rfc:make-reflection-setaccessible-no-op

PHP 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();

https://3v4l.org/ousrD :

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.

Make reflection setAccessible() no-op
Real name Yes No
alcaeus (alcaeus)  
asgrim (asgrim)  
beberlei (beberlei)  
bwoebi (bwoebi)  
cmb (cmb)  
crell (crell)  
daverandom (daverandom)  
derick (derick)  
galvao (galvao)  
jasny (jasny)  
kalle (kalle)  
kelunik (kelunik)  
kguest (kguest)  
lcobucci (lcobucci)  
marandall (marandall)  
nicolasgrekas (nicolasgrekas)  
nikic (nikic)  
ocramius (ocramius)  
petk (petk)  
ralphschindler (ralphschindler)  
rasmus (rasmus)  
reywob (reywob)  
sebastian (sebastian)  
sergey (sergey)  
stas (stas)  
svpernova09 (svpernova09)  
tandre (tandre)  
theodorejb (theodorejb)  
trowski (trowski)  
twosee (twosee)  
wyrihaximus (wyrihaximus)  
Final result: 31 0
This poll has been closed.
rfc/make-reflection-setaccessible-no-op.txt · Last modified: 2021/07/08 08:57 by nikic