rfc:pass_scope_to_magic_accessors

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Last revisionBoth sides next revision
rfc:pass_scope_to_magic_accessors [2023/01/19 14:55] nicolasgrekasrfc:pass_scope_to_magic_accessors [2023/01/19 17:39] nicolasgrekas
Line 3: Line 3:
   * Date: 2023-01-19   * Date: 2023-01-19
   * Author: Nicolas Grekas <nicolasgrekas@php.net>, Ilija Tovilo <tovilo.ilija@gmail.com>   * Author: Nicolas Grekas <nicolasgrekas@php.net>, Ilija Tovilo <tovilo.ilija@gmail.com>
-  * Status: Draft+  * Status: Under Discussion
   * First Published at: https://wiki.php.net/rfc/pass_scope_to_magic_accessors   * First Published at: https://wiki.php.net/rfc/pass_scope_to_magic_accessors
   * Implementation: https://github.com/iluuu1994/php-src/pull/46   * Implementation: https://github.com/iluuu1994/php-src/pull/46
Line 14: Line 14:
 This RFC proposes to pass their calling scope to magic accessors: ''%%__get()%%'', ''%%__set()%%'', ''%%__isset()%%'', ''%%__unset()%%'', ''%%__call()%%'' and ''%%__callStatic()%%''. This RFC proposes to pass their calling scope to magic accessors: ''%%__get()%%'', ''%%__set()%%'', ''%%__isset()%%'', ''%%__unset()%%'', ''%%__call()%%'' and ''%%__callStatic()%%''.
  
-This would help to properly implement visibility-related logic. Right now, we have to call ''debug_backtrace()'' to write scope-sensitive logic, but it's very difficult to get it right: accounting for inheritance is hard (calling e.g. ''%%parent::__get()%%'' changes the outcome of ''debug_backtrace()'' inside this ''%%parent::__get()%%'' method.) Passing 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. Last but not least, removing calls to ''debug_backtrace()'' would improve the performance of such magic accessors.+ 
 +The calling scope is defined as the name of the class that is calling a magic method. It's a string. It can also be null when a magic method is called outside of any class context, as is the case when calling them from the global scope or from the body of a named function. For closures, their scope is the class name they were created in (if any), or their rebound scope when using ''Closure::bind()''. The calling scope is also the piece of information that is found under key ''"class"'' of frames returned by ''debug_backtrace()''
 + 
 +Here are some examples: 
 + 
 +<PHP> 
 +class Foo { 
 +    private $abc; 
 +     
 +    public function __construct() 
 +    { 
 +        unset($this->abc); // enables magic methods when accessing the property 
 +    } 
 +     
 +    public function __get($name) { 
 +        var_dump(debug_backtrace()[1]['class'] ?? null); 
 +    } 
 +
 + 
 +$foo = new Foo(); 
 + 
 +$foo->abc; // NULL 
 + 
 +class Bar { 
 +    public function __construct($foo) { 
 +        $foo->abc; 
 +    } 
 +
 + 
 +$bar = new Bar($foo); // string(3) "Bar" 
 + 
 +$f = fn ($foo) => $foo->abc; 
 + 
 +$f($foo); // NULL 
 + 
 +$g = \Closure::bind($f, null, Bar::class); 
 + 
 +$g($foo); // string(3) "Bar" 
 +</PHP> 
 + 
 +Passing the calling scope to magic accessors would help to properly implement visibility-related logic. Right now, we have to call ''debug_backtrace()'' to write scope-sensitive logic, but it's very difficult to get it right: accounting for inheritance is hard (calling e.g. ''%%parent::__get()%%'' changes the outcome of ''debug_backtrace()'' inside this ''%%parent::__get()%%'' method.) Passing 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. Last but not least, removing calls to ''debug_backtrace()'' would improve the performance of such magic accessors.
  
 Right now, the logic to access the calling scope is quite involving. This example doesn't handle chained inheritance calls: Right now, the logic to access the calling scope is quite involving. This example doesn't handle chained inheritance calls:
Line 57: Line 97:
 The PHP engine will pass the calling scope to magic accessors even if they don't declare the corresponding argument. The corresponding value will always be accessible via ''func_get_arg()''. The PHP engine will pass the calling scope to magic accessors even if they don't declare the corresponding argument. The corresponding value will always be accessible via ''func_get_arg()''.
  
-This ensures that userland will be able to write code that works on both PHP <= 8.2 and PHP >= 8.3. (As a reminder, the engine forbids adding any extra arguments to magic methods: it's not allowed to declare a ''%%__get($name, $scope)%%'' on PHP <= 8.2. - but it //is// allowed to //call// this method with extra arguments for userland classes.)+This ensures that userland will be able to write code that works on both PHP <= 8.2 and PHP >= 8.3. (As a reminder, the engine forbids adding any extra arguments to the signature of magic methods: it's not allowed to declare a ''%%__get($name, $scope)%%'' on PHP <= 8.2. Note also that it is allowed to //call// this method with extra arguments, at least for userland classes.)
  
-For consistency with the checks in place for the currently accepted arguments, the engine will throw a fatal error when the new argument is declared with a type that is not compatible with ''string|null'.+For consistency with the checks in place for the currently accepted arguments, the engine will throw a fatal error when the new argument is declared with a type that is not compatible with ''string|null''.
  
 ===== Proposed PHP Version ===== ===== Proposed PHP Version =====
Line 67: Line 107:
 ===== RFC Impact ===== ===== RFC Impact =====
  
-===== Unaffected PHP Functionality =====+==== Unaffected PHP Functionality ====
  
 Existing magic accessors won't be required to declare the new argument. Existing magic accessors won't be required to declare the new argument.
rfc/pass_scope_to_magic_accessors.txt · Last modified: 2023/05/09 16:25 by nicolasgrekas