[PHP-DEV] [RFC] isReadable/isWriteable property reflection

In other news, Ilija and I said a year ago that we'd take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

--
  Larry Garfield
  larry@garfieldtech.com

Hi

Am 2025-11-06 00:24, schrieb Larry Garfield:

In other news, Ilija and I said a year ago that we'd take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

PHP: rfc:isreadable-iswriteable

I dislike implicitly “scope-dependent” functions, since they effectively act like magic. This probably makes it harder to understand for humans and static analysis tools alike. I would therefore suggest making the `$scope` parameter required. A user can just pass `static::class` themselves and static analysis tools can use `class-string|null` instead of `class-string|"static"|null` as their expected parameter type.

As for the magic method logic: I would suggest to ignore the presence of __get() and __set(). This more closely aligns with the direction PHP goes towards and is also easy to work around by checking with `method_exists()` whether any such a method exists - the reverse is not true.

Best regards
Tim Düsterhus

On Fri, Nov 7, 2025, at 5:03 AM, Tim Düsterhus wrote:

Hi

Am 2025-11-06 00:24, schrieb Larry Garfield:

In other news, Ilija and I said a year ago that we'd take a swing at
adding isReadable/isWriteable methods to ReflectionProperty. Took a
while, but here we are. A strangely small RFC from us:

PHP: rfc:isreadable-iswriteable

I dislike implicitly “scope-dependent” functions, since they effectively
act like magic. This probably makes it harder to understand for humans
and static analysis tools alike. I would therefore suggest making the
`$scope` parameter required. A user can just pass `static::class`
themselves and static analysis tools can use `class-string|null` instead
of `class-string|"static"|null` as their expected parameter type.

Given that the 90% or more case is likely to be "from my current context", making that the default seems the most ergonomic. (The counter argument, I suppose, is that this is hardly going to be a super-common routine to call so ergonomics don't matter.)

stack-inspection isn't new. `Closure::getCurrent()` is the most recent example, so we don't think it's especially problematic.

If there's a preference for avoiding magic strings, it would be easy enough to use an enum instead. Something like:

enum CallerScope {
  case Caller;
  case Global;
}

function isReadable(string|CallerScope $scope = CallerScope::Caller, ?object $objecg = null) { ... }

`Caller` would still do the same stack inspection, but it makes the type more tightly controlled and eliminates a nullable.

As for the magic method logic: I would suggest to ignore the presence of
__get() and __set(). This more closely aligns with the direction PHP
goes towards and is also easy to work around by checking with
`method_exists()` whether any such a method exists - the reverse is not
true.

thumbs-up.gif

--Larry Garfield

Hi

On 11/7/25 20:12, Larry Garfield wrote:

(The counter argument, I suppose, is that this is hardly going to be a super-common routine to call so ergonomics don't matter.)

Yes. I expect this to mostly be useful deep within frameworks or specialized libraries. The type of code where these functions are likely to be used are very likely to be functions with an above-average complexity and having explicit code there will help keep the complexity low. Also it's not like `static::class` is particularly complicated to use.

One issue I'm seeing with the “magic context” logic is that the code will suddenly break when the call to `->isReadable()` is moved into a helper function. Particularly when you consider the __get() use case that I mentioned below: Folks that are interested in wanting to learn whether there's a property that is actually readable *or* a magic getter might want to put this combined logic into a helper - and then the implicit scope will no longer work.

stack-inspection isn't new. `Closure::getCurrent()` is the most recent example, so we don't think it's especially problematic.

The difference is that `Closure::getCurrent()` is a single-purpose function that is explicitly defined to operate on the current scope. It specifically throws an Error when misused. `->isReadable()` OTOH will silently misbehave.

AFAICT it would also be the first reflection function that has dynamic behavior.

If there's a preference for avoiding magic strings, it would be easy enough to use an enum instead. Something like:

My issue is not primarily with the “magic string”, but with the quite significant behavioral difference depending on the parameter value.

Best regards
Tim Düsterhus

Le jeu. 6 nov. 2025 à 00:28, Larry Garfield <larry@garfieldtech.com> a écrit :

In other news, Ilija and I said a year ago that we’d take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

https://wiki.php.net/rfc/isreadable-iswriteable

Thanks for this.

I also think the auto-scope is a good idea. You state that 90% of use cases will need this but my experience doesn’t back this claim. The only cases where I had to check for read/writeable were out of the local scope, so I’d say 100% of my experience goes against that 90% number :wink: Joke aside, it’d be just fine to let ppl be explicit. That’s better than “oops I forgot to give the correct scope” bugs.

About magic methods, one unsets a property only to have __get/__set called. Existing code works with this assumption. This means we have to return true IMHO. Magic methods are just generic hooks also. Which means they should behave the same.

Nicolas

Sorry, typo:

I also think the auto-scope is a not good idea

Le jeu. 4 déc. 2025 à 15:05, Nicolas Grekas <nicolas.grekas+php@gmail.com> a écrit :

Le jeu. 6 nov. 2025 à 00:28, Larry Garfield <larry@garfieldtech.com> a écrit :

In other news, Ilija and I said a year ago that we’d take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

https://wiki.php.net/rfc/isreadable-iswriteable

Thanks for this.

I also think the auto-scope is a good idea. You state that 90% of use cases will need this but my experience doesn’t back this claim. The only cases where I had to check for read/writeable were out of the local scope, so I’d say 100% of my experience goes against that 90% number :wink: Joke aside, it’d be just fine to let ppl be explicit. That’s better than “oops I forgot to give the correct scope” bugs.

About magic methods, one unsets a property only to have __get/__set called. Existing code works with this assumption. This means we have to return true IMHO. Magic methods are just generic hooks also. Which means they should behave the same.

Nicolas

On Thu, Dec 4, 2025, at 8:05 AM, Nicolas Grekas wrote:

Le jeu. 6 nov. 2025 à 00:28, Larry Garfield <larry@garfieldtech.com> a écrit :

In other news, Ilija and I said a year ago that we'd take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

PHP: rfc:isreadable-iswriteable

Thanks for this.
I also think the auto-scope is [NOT] a good idea. You state that 90% of use
cases will need this but my experience doesn't back this claim. The
only cases where I had to check for read/writeable were out of the
local scope, so I'd say 100% of my experience goes against that 90%
number :wink: Joke aside, it'd be just fine to let ppl be explicit. That's
better than "oops I forgot to give the correct scope" bugs.

About magic methods, one unsets a property only to have __get/__set
called. Existing code works with this assumption. This means we have to
return true IMHO. Magic methods are just generic hooks also. Which
means they should behave the same.

Nicolas

Well, the only people who seem to have an opinion don't like "static", so we've removed it. RFC updated.

As for __get/__set, that's so far one vote for ignore (Tim), and one for always-true (Nicolas). Not a consensus. :slight_smile:

Nicolas, can you clarify with an example if/how ignore would break things?

I think once we settle that question and the cooldown passes we're ready for a vote, though at this point that means January.

--Larry Garfield

Le jeu. 4 déc. 2025 à 17:39, Larry Garfield <larry@garfieldtech.com> a écrit :

On Thu, Dec 4, 2025, at 8:05 AM, Nicolas Grekas wrote:

Le jeu. 6 nov. 2025 à 00:28, Larry Garfield <larry@garfieldtech.com> a écrit :

In other news, Ilija and I said a year ago that we’d take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

https://wiki.php.net/rfc/isreadable-iswriteable

Thanks for this.
I also think the auto-scope is [NOT] a good idea. You state that 90% of use
cases will need this but my experience doesn’t back this claim. The
only cases where I had to check for read/writeable were out of the
local scope, so I’d say 100% of my experience goes against that 90%
number :wink: Joke aside, it’d be just fine to let ppl be explicit. That’s
better than “oops I forgot to give the correct scope” bugs.

About magic methods, one unsets a property only to have __get/__set
called. Existing code works with this assumption. This means we have to
return true IMHO. Magic methods are just generic hooks also. Which
means they should behave the same.

Nicolas

Well, the only people who seem to have an opinion don’t like “static”, so we’ve removed it. RFC updated.

As for __get/__set, that’s so far one vote for ignore (Tim), and one for always-true (Nicolas). Not a consensus. :slight_smile:

Nicolas, can you clarify with an example if/how ignore would break things?

I think once we settle that question and the cooldown passes we’re ready for a vote, though at this point that means January.

I can try to build a synthetic example but the gist is:
A class that starts with only properties and no __get() should be able to move to a __get()-based hooking in a later version without breaking code that uses isReadable().
That on its own should be enough to settle the desired behavior :slight_smile:

On Thu, Dec 4, 2025, at 10:47 AM, Nicolas Grekas wrote:

Le jeu. 4 déc. 2025 à 17:39, Larry Garfield <larry@garfieldtech.com> a écrit :

On Thu, Dec 4, 2025, at 8:05 AM, Nicolas Grekas wrote:
> Le jeu. 6 nov. 2025 à 00:28, Larry Garfield <larry@garfieldtech.com> a écrit :
>> In other news, Ilija and I said a year ago that we'd take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:
>>
>> PHP: rfc:isreadable-iswriteable
>>
>
>
> Thanks for this.
> I also think the auto-scope is [NOT] a good idea. You state that 90% of use
> cases will need this but my experience doesn't back this claim. The
> only cases where I had to check for read/writeable were out of the
> local scope, so I'd say 100% of my experience goes against that 90%
> number :wink: Joke aside, it'd be just fine to let ppl be explicit. That's
> better than "oops I forgot to give the correct scope" bugs.
>
> About magic methods, one unsets a property only to have __get/__set
> called. Existing code works with this assumption. This means we have to
> return true IMHO. Magic methods are just generic hooks also. Which
> means they should behave the same.
>
> Nicolas

Well, the only people who seem to have an opinion don't like "static", so we've removed it. RFC updated.

As for __get/__set, that's so far one vote for ignore (Tim), and one for always-true (Nicolas). Not a consensus. :slight_smile:

Nicolas, can you clarify with an example if/how ignore would break things?

I think once we settle that question and the cooldown passes we're ready for a vote, though at this point that means January.

I can try to build a synthetic example but the gist is:
A class that starts with only properties and no __get() should be able
to move to a __get()-based hooking in a later version without breaking
code that uses isReadable().
That on its own should be enough to settle the desired behavior :slight_smile:

So you want to be able to transition from:

class Foo
{
  public private(set) string $name;
}

To

class Foo
{
  private array $vals = ;

  public function __get(string $name)
  {
    return $this->vals[$name] ?? throw new Exception();
  }
}

Is that right? Because both approaches would result in changes in some situations there.

If __get is ignored and the value is set, then a global isReadable check will go from true to false in that transition.

If __get always returns true and the value is still uninitialized, a global isReadable check will go from false to true in that transition.

Either way, that's not a fully safe transition to make.

--Larry Garfield

Le jeu. 4 déc. 2025 à 18:03, Larry Garfield <larry@garfieldtech.com> a écrit :

On Thu, Dec 4, 2025, at 10:47 AM, Nicolas Grekas wrote:

Le jeu. 4 déc. 2025 à 17:39, Larry Garfield <larry@garfieldtech.com> a écrit :

On Thu, Dec 4, 2025, at 8:05 AM, Nicolas Grekas wrote:

Le jeu. 6 nov. 2025 à 00:28, Larry Garfield <larry@garfieldtech.com> a écrit :

In other news, Ilija and I said a year ago that we’d take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

https://wiki.php.net/rfc/isreadable-iswriteable

Thanks for this.
I also think the auto-scope is [NOT] a good idea. You state that 90% of use
cases will need this but my experience doesn’t back this claim. The
only cases where I had to check for read/writeable were out of the
local scope, so I’d say 100% of my experience goes against that 90%
number :wink: Joke aside, it’d be just fine to let ppl be explicit. That’s
better than “oops I forgot to give the correct scope” bugs.

About magic methods, one unsets a property only to have __get/__set
called. Existing code works with this assumption. This means we have to
return true IMHO. Magic methods are just generic hooks also. Which
means they should behave the same.

Nicolas

Well, the only people who seem to have an opinion don’t like “static”, so we’ve removed it. RFC updated.

As for __get/__set, that’s so far one vote for ignore (Tim), and one for always-true (Nicolas). Not a consensus. :slight_smile:

Nicolas, can you clarify with an example if/how ignore would break things?

I think once we settle that question and the cooldown passes we’re ready for a vote, though at this point that means January.

I can try to build a synthetic example but the gist is:
A class that starts with only properties and no __get() should be able
to move to a __get()-based hooking in a later version without breaking
code that uses isReadable().
That on its own should be enough to settle the desired behavior :slight_smile:

So you want to be able to transition from:

class Foo
{
public private(set) string $name;
}

To

class Foo
{
private array $vals = ;

public function __get(string $name)
{
return $this->vals[$name] ?? throw new Exception();
}
}

Is that right? Because both approaches would result in changes in some situations there.

If __get is ignored and the value is set, then a global isReadable check will go from true to false in that transition.

If __get always returns true and the value is still uninitialized, a global isReadable check will go from false to true in that transition.

Either way, that’s not a fully safe transition to make.

–Larry Garfield

Nah, I’m thinking about a scenario very close to what you talk about in the RFC: unset + __get

From:
class Foo
{
public int $abc;

public function __construct()
{
$this->abc = stuff();
}
}

To:

class Foo
{
public int $abc;

public function __construct()
{
unset($this->abc);
}

public function __get($name)
{
if (‘abc’ === $name) {
return $this->abc = stuff();
}
}

}

Hey Larry,

On 6.11.2025 00:24:52, Larry Garfield wrote:

In other news, Ilija and I said a year ago that we'd take a swing at adding isReadable/isWriteable methods to ReflectionProperty. Took a while, but here we are. A strangely small RFC from us:

PHP: rfc:isreadable-iswriteable

Have you considered returning false on isReadable() and isWritable() for methods, whose only statement is throwing? I.e. when a getter or setter unconditionally throws without any other statements present, they are not marked as readable or writable.

Otherwise this essentially "punishes" providing better exceptions in a getter or setter, and serves also cases where the LSP inheritance forces presence of a getter/setter, but still shall not be allowed on the specific instance.

Thanks,
Bob