rfc:make-reflection-setaccessible-no-op

This is an old revision of the document!


PHP RFC: #[Deprecated] Attribute

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

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

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?

Patches and Tests

rfc/make-reflection-setaccessible-no-op.1623601443.txt.gz · Last modified: 2021/06/13 16:24 by ocramius