Hi Tim,
Le ven. 29 mai 2026 à 12:01, Tim Düsterhus <tim@bastelstu.be> a écrit :
Hi
Am 2026-05-09 15:14, schrieb Nicolas Grekas:
[…]
need the same adjustments. I don’t think the stated benefits are
enough to warrant a full migration to a new method.
[…]
[…]
I now finally got around to reading your RFC in-depth and have also read
through the related GH-12695 issue to get the full picture.
I share Ilija’s opinion in that this feels very much like a
“sledgehammer to crack a nut” approach to fix something that is
effectively the result of a userland implementation error in a feature
that nowadays has better replacements in many situations: Namely
forgetting to implement __isset() when __get() is implemented; and
not implementing a null check in __isset(). In fact the first issue
would not be solved by the proposal, since instead of forgetting to
implement __isset(), folks would just forget to implement
__exists().
Given that the RFC can naturally only ship in a new PHP version and
users will not upgrade right-away, it will take years until folks can
actually rely on the updated behavior and they will need to maintain
their existing workarounds until then. Specifically for the “lazy
proxies” use case that also was the motivation for GH-12695 my
understanding is that this is already solved in a much cleaner way since
PHP 8.4 with native lazy objects. Why would I want to wait for PHP 8.6
to be the baseline, when the better solution is already available with
8.4? Similarly other “lazy initialization” use cases for individual
properties can already be solved with property hooks in a cleaner way.
That leaves the “JsonRecord” use case, where the magic method doesn’t do
anything by itself either, since folks would need to manually call it to
make the “exists but is null” distinction. It could just be a regular
method there - as already done in userland collection classes that just
have explicit ->has() and ->get() methods instead of relying on
magic methods. These notably also allow for much cleaner access to
fields that are not valid PHP identifiers (e.g. fields starting with a
digit or fields containing spaces).
I would treat this as a documentation issue, where it’s not clearly
specified that users are expected to implement a null check in
__isset() and that users are expected to also implement __isset()
when implementing __get(). Perhaps the latter could even be made a
warning. That leaves the issue in GH-12695 which could then be fixed as
a master-only bugfix. While this would still require folks to carry
their workarounds until they can rely on PHP 8.6, it would not make the
language any more complex by requiring folks to learn yet another
pattern.
Thanks for the review.
Treating magic accessors as legacy conflicts with how widely-used PHP code actually works: Laravel Eloquent is built on __get/__set, and a long tail of infrastructure sits on the same primitive, with legitimate use of them (runtime-discovered properties typically).
Also: Native lazy objects don’t cover laziness on userland subclasses of internal classes, nor interface-based proxies or decorator patterns where the target shape is dynamic.
Property hooks need declared property names, which is the constraint that will continue to drive users to magic accessors in the first place.
If we agree magic accessors stay load-bearing (the alternative would be a deprecation path that nobody is proposing), fixing their broken documented semantics is maintainer responsibility.
The underlying issue isn’t a userland implementation error: even a perfectly null-checking __isset cannot distinguish “set to null” from “missing” on a magic property.
The method returns one bit and is being asked two questions.
The ?? RFC also explicitly defines $x ?? $y as isset($x) ? $x : $y.
That contract is currently broken on magic properties. Documentation can’t fix a contract; the engine has to.
Patching __isset for GH-12695 alone doesn’t restore it either; it would need to change isset() semantics, a universal BC break.
On “people will just forget __exists”: a class with only __get() today produces broken isset() (always false) and empty() (always true).
Adding __exists() { return true; } fixes both for free. The “Recommended floor for classes with __get” section in the RFC walks through this.
It is strictly less work than the existing __get + __isset pattern.
The “->has() / ->get() methods” suggestion is missing the point by abandoning property syntax, which is the reason magic accessors exist in the first place.
About __get-without-__isset (nor __exists), a warning could be fine to me as an adjacent diagnostic.
I’d defer this to a follow up RFC, because this needs its own impact analysis and is something related yet separate.
On the master-only property-materialization fix: as I noted to Ilija, I’m fine with decoupling it from this RFC.
The remaining motivations (the structural ambiguity, the documented-contract violation, the early-adoption convention via method_exists()) stand on their own.
On the timeline concern: on PHP 8.5 and earlier, __exists() is a regular method, so libraries can already probe method_exists($obj, ‘__exists’) and dispatch through it as a convention today. Adoption doesn’t have to wait for PHP 8.6 to be universally deployed.
Cheers,
Nicolas