Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
<rant> Magic methods are not supposed to _use_ dynamic properties... they are supposed to _handle_ access _to_ inaccessible (`protected` or `private`) or non-existing properties *sigh*. What's the point of having the magic methods in place otherwise ? </rant> The `WP_Object_Cache` class introduced the magic `__[isset|get|set|unset]()` methods in WP 4.0.0, but those methods were incorrectly implemented. 1. They contain no "allow list" of which `private`/`protected` properties are supposed to still be accessible, which in effect means any newly added `private`/`protected` properties would all still be treated as `public`. 2. They dynamically set and gave access to new (`public`) properties when an undeclared property was encountered. This basically leaves the class wide open and negates the protection the magic methods (and visibility modifiers) were _intended_ to provide. Unfortunately, this code has existed in WordPress for too long. Changing this now would constitute a massive BC-break. However, the magic method implementation as is, is hugely problematic in light of the PHP 8.2 dynamic properties deprecation and the eventual intended removal of dynamic properties. So this needs fixing _without_ breaking BC, but _with_ protection for potential future `protected`/`private` properties being added. The fix I'm proposing does exactly that, by: 1. Storing undeclared properties being set on the class in a `private` `$arbitrary_props` array. 2. Adding an "allow list" with the names of those `protected`/`private` properties which should remain accessible via the magic methods. The use of this "allow list" makes sure that any new `protected`/`private` properties added at a later date will no longer be accessible via the magic methods. This includes the two properties I'm adding in this commit: those will be properly protected against interference from outside now. 3. Fixing the methods themselves to no longer use dynamic property access, but use the `$arbitrary_props` array for undeclared properties. 4. Fixing the methods themselves to respect the allow list. 5. For the new _inaccessible_ properties, an `OutOfBoundsException` will be thrown if any attempt to retrieve their value (`__get()`) or overwrite them (`__set()`) is made. This is not a BC-break as this doesn't affect any pre-existing properties. The exception will only be thrown for new properties, i.e. properties which are introduced with this patch or after. Calls to `__isset()` will yield `false` and `__unset()` will silently ignore the property, which is in line with the PHP native and expected behaviour. 6. When the value of a property which does not exist and hasn't been (dynamically) declared is requested, an `Undefined property` warning will be thrown. PHP would previously natively throw this warning (warning since PHP 8.0, notice in PHP < 8.0). This is now emulated to ensure developer mistakes are not hidden away and developers actually are provided notice of these. The only difference is that for PHP < 8.0, the message has been elevated from a "notice" to a "warning". The only time that this message will be emitted is in case of developer error, so this message elevation is IMO not a BC-break. As for the choice between "notice" and "warning", I've chosen to stay in line with the latest PHP versions, especially as it has already been decided that this warning will be elevated in PHP 9.0 to a fatal error, so preventing those developer errors (or getting them fixed) seems prudent. Take note of the use of `property_exists()` and `array_key_exists()` in the magic methods instead of using `isset()`. A property may be set to `null` on purpose and the magic methods should handle that situation correctly. This also means that for _declared_ properties without a value, no error message level elevation is done. The `__get()` method only does a `property_exists()` check on those, no `isset()`, which means the PHP native error handling will kick in and throw a notice in PHP < 8.0 and a warning in PHP 8.0. Includes fixing the magic `__set()` method to not return as it is supposed to be a `void` method. While this could be considered a BC-break, the return value would already be ignored due to how PHP handles magic methods, so in reality, this change will not make any difference. > The return value of __set() is ignored because of the way PHP processes the assignment operator. Includes updating the tests to expect a warning for the "Undefined property" notice for undeclared properties, independently of the PHP version. Refs: * https://www.php.net/manual/en/language.oop5.overloading.php#object.set * https://www.php.net/manual/en/class.outofboundsexception.php * https://wiki.php.net/rfc/undefined_property_error_promotion * php/php-src#7786 These magic methods were originally introduced via changesets [28502](https://core.trac.wordpress.org/changeset/28502), [28521](https://core.trac.wordpress.org/changeset/28521), [28524](https://core.trac.wordpress.org/changeset/28524).
- Loading branch information