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
--
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:
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
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: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
$scopeparameter required. A user can just passstatic::class
themselves and static analysis tools can useclass-string|nullinstead
ofclass-string|"static"|nullas 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
(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:
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 ;) 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: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 ;) 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
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:
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 ;) 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. :-)
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 :
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: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 ;) 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. :-)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 :)
Le jeu. 4 déc. 2025 à 17:39, Larry Garfield larry@garfieldtech.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:
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 ;) 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. :-)
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 :)
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 :
Le jeu. 4 déc. 2025 à 17:39, Larry Garfield larry@garfieldtech.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: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 ;) 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. :-)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 :)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,
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:
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
Hey Larry,
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:
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
Hi Bob.
We've discussed it privately, and feel it would be an unprecedented amount of magic behavior to introduce in what is otherwise a fairly small utility RFC. Nowhere else does PHP do that kind of deep runtime introspection, and it's a rabbit hole much larger than we want to tackle. It's also a non-trivial problem to solve. For throw new Exception();, the opcodes look something like V1 = NEW; DO_FCALL; THROW V1;. That's fairly straight-forward. But what about:
- throw new Exception(getErrorMessage());
- throw new Exception(cond() ? 'Message 1' : 'Message 2');
- $message = getErrorMessage(); throw new Exception($message);
- if (cond()) { $message = 'Message 1'; } else { $message = 'Message 2'; } throw new Exception($message);
It's very hard to draw an arbitrary line to decide what is and what isn't allowed. Handling these cases correctly would require a significant amount of complexity, and would still be incomplete. If making small, seemingly benign changes can break the exception detection, that's arguably worse than no detection at all.
For now we'd rather just avoid that rabbit hole.
--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:
A few updates based on the discussion here:
- We now account for static properties. (What a concept.)
- The
staticmagic string is gone. - We have decided on an approach for magic methods. The updated RFC text explains it in more detail, but in short, "if __isset(), then use that to determine readable. Otherwise, __get() implies readable. __set() implies writeable." That should handle the use cases Nicolas was interested in.
We consider the RFC feature complete at this point. Baring any further substantive discussion, expect a vote in January after the blackout period ends.
--Larry Garfield
Hi
Am 2025-12-17 16:26, schrieb Larry Garfield:
- We have decided on an approach for magic methods. The updated RFC
text explains it in more detail, but in short, "if __isset(), then use
that to determine readable. Otherwise, __get() implies readable.
__set() implies writeable." That should handle the use cases Nicolas
was interested in.
That works for me. I didn't feel particularly strongly either way.
We consider the RFC feature complete at this point. Baring any further
substantive discussion, expect a vote in January after the blackout
period ends.
I don't have further comments about the semantics themselves, but have
one further clarification question:
The property has not been unset(). If it has, follow the same __isset
check as above
Should this read “the same __get check” instead?
And one note with regard to process: Don't forget to add a link to the
discussion (https://news-web.php.net/php.internals/129101) to the RFC.
Best regards
Tim Düsterhus
Hi
Am 2025-12-17 16:26, schrieb Larry Garfield:
- We have decided on an approach for magic methods. The updated RFC
text explains it in more detail, but in short, "if __isset(), then use
that to determine readable. Otherwise, __get() implies readable.
__set() implies writeable." That should handle the use cases Nicolas
was interested in.That works for me. I didn't feel particularly strongly either way.
We consider the RFC feature complete at this point. Baring any further
substantive discussion, expect a vote in January after the blackout
period ends.I don't have further comments about the semantics themselves, but have
one further clarification question:The property has not been unset(). If it has, follow the same __isset
check as aboveShould this read “the same __get check” instead?
Hm, yes, you are correct. Updated.
And one note with regard to process: Don't forget to add a link to the
discussion (https://news-web.php.net/php.internals/129101) to the RFC.
Added.
Oh, and one other note: It turns out that in English, both "writeable" and "writable" are nominally valid. The latter seems more common (and my spellchecker only recognizing the latter), so we've standardized on the non-E version throughout the RFC. (The code already was.)
--Larry Garfield
Hi
Am 2025-12-17 20:48, schrieb Larry Garfield:
Oh, and one other note: It turns out that in English, both "writeable"
and "writable" are nominally valid. The latter seems more common (and
my spellchecker only recognizing the latter), so we've standardized on
the non-E version throughout the RFC. (The code already was.)
As a non-native speaker, I confirm that “writable” without the “e” is
what looks more familiar to me. It's also consistent with
https://www.php.net/manual/en/function.is-writable.php (although that
one has an alias https://www.php.net/manual/en/function.is-writeable.php
- but since the one with the “e” is the alias, it is the less preferable
variant).
Best regards
Tim Düsterhus
Le mer. 17 déc. 2025 à 16:29, 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:A few updates based on the discussion here:
- We now account for static properties. (What a concept.)
- The
staticmagic string is gone.- We have decided on an approach for magic methods. The updated RFC text
explains it in more detail, but in short, "if __isset(), then use that to
determine readable. Otherwise, __get() implies readable. __set() implies
writeable." That should handle the use cases Nicolas was interested in.We consider the RFC feature complete at this point. Baring any further
substantive discussion, expect a vote in January after the blackout period
ends.
Hi, thanks for the update!
I read the updated description and that looks great to me, thanks.
Nicolas
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:
Hi folks.
The holiday blackout period is over, and there's nothing else really to discuss on this PR, so consider this an Intent to Vote notice for the isReadable RFC.
I'll call the vote later this week, baring any new serious constructive feedback.
--Larry Garfield
Le 13 janv. 2026 à 22:30, 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:Hi folks.
The holiday blackout period is over, and there's nothing else really to discuss on this PR, so consider this an Intent to Vote notice for the isReadable RFC.
I'll call the vote later this week, baring any new serious constructive feedback.
--Larry Garfield
Hi,
The RFC says:
To use “my current scope,” the static::class construct is an easy way to specify “whatever class this code is running in.”
The code is running in self::class or __CLASS__, not in static::class.
As noted in the RFC, isReadable() will give unavoidable false-positives (returning true although attempt to read the property will error). But there is also a false-negative:
class C {
function __construct(
readonly private mixed $foo
) { }
function __get($x) {
if ($x === 'foo') {
return $this->foo;
}
throw new Error('Undefined property '.$x);
}
function __isset($x) {
if ($x === 'foo')
return isset($this->foo); // not `true`, otherwise `isset(new C(null)->foo)` will return an incorrect result.
}
return false;
}
}
$c = new C(null);
// will return false, although it is readable
var_dump(new ReflectionProperty($c, 'foo')->isReadable(null, $c));
One could skip the __isset() check in order to avoid the false-negative, at the cost of more false-positives. I don’t know what is best, because I tend to avoid __get() like the plague anyway.
—Claude
Le 13 janv. 2026 à 22:30, 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:Hi folks.
The holiday blackout period is over, and there's nothing else really to discuss on this PR, so consider this an Intent to Vote notice for the isReadable RFC.
I'll call the vote later this week, baring any new serious constructive feedback.
--Larry Garfield
Hi,
The RFC says:
To use “my current scope,” the
static::classconstruct is an easy way to specify “whatever class this code is running in.”The code is running in
self::classor__CLASS__, not instatic::class.
Hm, good point. I've changed to to self::class, which I believe should only be an Editorial change. (There's no change to the design, just to the explanation, and it's arguably a typo.) I'll give it another day before opening the vote just in case.
As noted in the RFC,
isReadable()will give unavoidable
false-positives (returningtruealthough attempt to read the property
will error). But there is also a false-negative:class C { function __construct( readonly private mixed $foo ) { } function __get($x) { if ($x === 'foo') { return $this->foo; } throw new Error('Undefined property '.$x); } function __isset($x) { if ($x === 'foo') return isset($this->foo); // not `true`, otherwise `isset(new C(null)->foo)` will return an incorrect result. } return false; } } $c = new C(null); // will return false, although it is readable var_dump(new ReflectionProperty($c, 'foo')->isReadable(null, $c));One could skip the
__isset()check in order to avoid the
false-negative, at the cost of more false-positives. I don’t know what
is best, because I tend to avoid__get()like the plague anyway.
As discussed earlier in the thread and noted in the RFC, as long as hooks and __get support arbitrary code, we can never guarantee no false positives or negatives. Based on the earlier discussion, the current setup seems like the best balance for the expected typical cases. So we'd rather stick with how it is now.
--Larry Garfield