====== PHP RFC: isReadable/Writable reflection methods ======
* Version: 0.9
* Date: 2024-11-28
* Author: Ilija Tovilo (tovilo.ilija@gmail.com), Larry Garfield (larry@garfieldtech.com)
* Status: In Discussion
* First Published at: http://wiki.php.net/rfc/isreadable-iswriteable
* Pull Request: https://github.com/php/php-src/pull/16209
===== Introduction =====
The ReflectionProperty::isPublic() method, by design, indicates only if a property has a "public" flag set on it, nothing more. Prior to PHP 8.1, that implicitly also meant "can be written to from scope outside the object." However, PHP 8.1 introduced ''readonly'' properties, which broke that assumption with implicit private-set visibility. The addition of explicit asymmetric visibility in PHP 8.4 further undermined that assumption. The result is that there is currently no straightforward way to determine at runtime if reading from or writing to a property would be allowed. This RFC attempts to provide such a utility.
===== Proposal =====
The ReflectionProperty object will be expanded with two additional methods, as defined below:
class ReflectionProperty
{
// ... All the existing functionality.
public function isReadable(?string $scope, ?object $object = null): bool {}
public function isWritable(?string $scope, ?object $object = null): bool {}
}
The behavior of the parameters is the same for both methods.
==== $scope ====
The $scope parameter specifies the scope from which we want to know if the operation is valid. Put another way, these methods can be read as "if I were to try to read/write this property from $scope, would that be allowed?"
The $scope parameter may have one of two values:
* ''null''. A ''null'' scope refers to the global scope. That is, "would it be allowed to read/write this property from global scope?" In practice, a stand-alone function is "global" for these purposes.
* A class name string. Any defined class name. "Would it be allowed to read/write this property from a method on this class?"
To use "my current scope," the static::class construct is an easy way to specify "whatever class this code is running in."
==== $object ====
The $object parameter is an optional object to analyze the property on. If not provided, the analysis will look only at static information on the property, and thus ignore information such as if a ''readonly'' property has already been written to.
==== Magic methods ====
The magic methods %%__get%% and %%__set%% pose an interesting challenge, especially when combined with an unset property. For reading, the presence of a %%__get%% method means that any arbitrary property name //might// be readable, including those that are defined but explicitly ''unset()'' (a common trick in the past for lazy initialization before hooks were available). For writing, the presence of a %%__set%% method means that any arbitrary property name //might// be writable, even if not defined.
Based on discussion on the Internals list, we have decided to take the following interpretation:
* If a property read would trigger a %%__get%% method and a %%__isset%% method is present, %%__isset%% will be called and its result will be returned.
* If a property read would trigger a %%__get%% method and no %%__isset%% method is present, it will be treated as readable.
* If a property write would otherwise trigger a %%__set%% method, it will be treated as writable.
==== Static properties ====
Static properties are supported. Their logic is essentially the same, except they do not check for magic methods. Additionally, passing an object is irrelevant as it would not be checked.
==== Considered factors ====
Both methods will examine the same information about a property, if available, to determine if the operation would be allowed.
''isReadable()''
* If the property is defined and readable from the passed scope
* and has no hooks or is backed, return true.
* and is virtual and readable return true only if the ''get'' hook is defined.
* If a %%__get%% hook is defined
* and a %%__isset%% hook is defined, return the result.
* and there is no %%__isset%% hook, return true
* If an object is provided, also confirm:
* The property is initialized
* The property has not been ''unset()''. If it has, follow the same %%__isset%% check as above.
''isWritable()''
* If the property is defined
* and has no hooks or is backed, and is writable from the passed scope, return true.
* and is virtual, return true only if the ''set'' hook is defined.
* If a %%__set%% hook is defined, return true
* If an object is provided, also confirm at least one of:
* the property is not ''readonly''
* the property is not yet initialized,
* or is reinitializable (__clone)
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)
Two, a ''get'' or ''set'' hook may throw an exception under arbitrarily complex circumstances. There is no way to evaluate that via reflection, so it's a gap that will necessarily always be there.
===== Backward Incompatible Changes =====
None.
===== Proposed PHP Version(s) =====
PHP 8.5
===== Proposed Voting Choices =====
Yes or no vote, 2/3 required to pass.
* Yes
* No
* Abstain
===== Implementation =====
After the project is implemented, this section should contain
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
- a link to the language specification section (if any)
===== References =====
Links to external references, discussions or RFCs
===== Rejected Features =====
* Allowing the "static" keyword for the ''$scope'' variable to indicate "current scope."
===== Changelog =====
* 2025-12-04 - Removed "static" keyword for ''$scope''
* 2025-12-13 - Defined rules for %%__set%% and %%__get%%
* 2025-12-16 - Add static property support