Re: Asymmetric visibility is a BC break

From: Date: Mon, 14 Oct 2024 03:22:13 +0000
Subject: Re: Asymmetric visibility is a BC break
References: 1 2 3 4 5 6 7 8 9 10 11  Groups: php.internals 
Request: Send a blank email to internals+get-125796@lists.php.net to get a copy of this message
On Mon, 14/10/2024 at 06:01, Larry Garfield <larry@garfieldtech.com> wrote: > > On Sun, Oct 13, 2024, at 9:37 PM, Valentin Udaltsov wrote: > > > > First of all, I have already agreed above that PHP does not have a BC > > break here. Now we are discussing the potential problems in the PHP > > ecosystem and how they could be mitigated. > > Ilija and I have discussed this issue a bit. > > The first issue is that isPublic() technically means "does this property have the public > flag set," and nothing more. Prior to 8.1, that implicitly also meant "can the property be > read and written to from public scope," because of how properties worked. (And same for > isProtected().) That implicit assumption became invalid in 8.1 with readonly, which stealth > introduced limited and not fully designed asymmetric visibility as well as properties that could not > be set multiple times from any scope. Full aviz in 8.4 doesn't change that. It just makes the > previous assumption change more apparent. The fact that no one seems to have reported it as an issue > until now suggests it's not a particularly widespread problem. In practice, if someone is > using reflection to determine the visibility of a property, they'll be writing to it through > reflection as well if at all. > > The best solution here is probably to just clarify the docs, which I will do as part of the > aviz docs that I have already been working on. cf: > https://github.com/php/doc-en/pull/3828 > > The second issue is that the behavior of isProtectedSet() / isPrivateSet() was not as clearly > defined in the RFC as it should have been. That's on us for not being clearer, as we apologize > for the oversight. > > Those methods follow the low level pattern of isPublic() , that is, they just report of a given > flag is set, not what the implications of that flag in various contexts are. That is consistent with > the rest of the reflection API, so we feel that is best left as-is. > > That still means the "so can I read/write this property or not?" question has no > simple operation for it. Again: it never did, we just kinda sorta had it indirectly and implicitly. > For that we feel the best answer, as well as least disruptive given we're in RCs, is dedicated > methods as Ilija has already described that take all property behavior and context into account. > (isReadable and isWriteable.) > > As a reminder, the concept is: > > $rProp->isReadable($obj); // Can this property on $obj be read from the calling scope? > $rProp->isReadable($obj, 'static'); // Same as previous. > $rProp->isReadable($obj, null); // Can this property on $obj be read from global scope? > $rProp->isReadable($obj, Foo::class); // Can this property on $obj be read from code inside > class Foo? > > $rProp->isWriteable($obj); // Can this property on object $obj be written from the calling > scope? > $rProp->isWriteable($obj, 'static'); // Same as previous. > $rProp->isWriteable($obj, null); // Can this property on object $obj be written from global > scope? > $rProp->isWriteable($obj, Foo::class); // Can this property on object $obj be written from > code inside class Foo? > > cf: https://github.com/php/php-src/pull/16209 > > (The use of null to indicate global scope is borrowed from Closure::bind(), which does the > same.) > > These methods do runtime analysis to see if a property should be readable/writeable. > Specifically: > > isReadable() > * Checks that the property is readable from the passed scope > * Checks that the property is initialized (i.e. not typed and never written to) > * Checks that the property is not virtual or has a get hook > isWritable() > * Checks that the property is writable (respecting symmetric and asymmetric properties) from > the passed scope > * Checks that the property is not readonly, is not yet initialized, or is reinitializable > (__clone) > * Checks that the property is not virtual or has a set hook > > Of note, this does not absolutely guarantee that a read/write will succeed. There's at > least two exceptions: One, some PHP built-in classes have effectively immutable properties but do > not use readonly or private(set). > Those would not be detected here, until and unless they are updated to use the now-available > mechanisms. (See, eg: https://github.com/php/php-src/issues/15309) > The other is that a get or set hook may throw an exception under various circumstances. There is no > way to evaluate that via reflection, so it's a gap that will necessarily always be there. > > Whether those methods are OK to add in the RC phase or if they should be left to early 8.5, and > if they would need a formal RFC, is up to the RMs to decide. RMs, what is your preference? > > --Larry Garfield Hi, Larry! Thank you very much for this response. I agree with every point. I have only one comment about the methods' signatures. They should not require an object, because properties can be static or the developer might want to check writable/readable without instantiating an object (in a code generator, for instance). Then it makes sense to make $object the 2nd parameter. This will also be consistent with Closure::bind(). isReadable(?string $scope = 'static', ?object $object = null): bool isWritable(?string $scope = 'static', ?object $object = null): bool -- Best regards, Valentin

« previous php.internals (#125796) next »