[php-src] Issue #9906: Add argument $callingScope to magic method get/set/isset/unset
| From: | nicolas-grekas | Date: | Tue, 08 Nov 2022 15:41:09 +0000 |
| Subject: | [php-src] Issue #9906: Add argument $callingScope to magic method get/set/isset/unset | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-242768@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/9906
Author: nicolas-grekas
### Description
I worked quite a lot with magic methods recently and I figured out that they should accept a extra
argument: the calling scope, as a class name (or null when not called from a class). This would help
to properly implement visibility-related logic. Right now, we have to call
debug_backtrace() to write such logics, but everybody forgets about it and more
annoyingly this doesn't work well with inheritance (calling parent::__get()
changes the outcome of debug_backtrace() inside this parent::__get
method.) Adding the calling scope would make this concern more evident and would solve the issue
with inheritance. It would also fix the edge case of calling a magic method via
ReflectionProperty::get/setValue, where looking up at debug_backtrace()
needs special care to extract the calling scope.
Before:
```php
class Foo extends Bar
{
public function __get($name): mixed
{
$frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS,
3)[1];
$callingScope = $frame['class'] ?? null;
if (\ReflectionProperty::class === $callingScope) {
$callingScope = $frame['object']->class;
}
// Now we can implement visibility-related logic for accessing $name
// Let's say we want to call parent::__get() and it uses the same logic to extract the
calling
// scope, it's now broken because instead of getting frame [1], it should use
frame [2],
// but knowing this requires more non trivial logic.
}
}
```
After:
```php
class Foo extends Bar
{
public function __get($name, ?string $callingScope = null): mixed
{
// $callingScope is populated with the value computed before
// And calling parent::__get($name, $callingScope) is done easily without resorting to any
specific logic
}
}
```