rfc:pass_scope_to_magic_accessors

Differences

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

Link to this comparison view

Next revision
Previous revision
Last revisionBoth sides next revision
rfc:pass_scope_to_magic_accessors [2023/01/19 14:50] – created 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 12: Line 12:
 ===== Proposal ===== ===== Proposal =====
  
-This RFC proposes to pass the 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.)+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.
Line 77: Line 117:
 ==== To Existing Extensions ==== ==== To Existing Extensions ====
  
-Extensions that declare magic accessors (e.g. soap) will need to declare the new argument. +Because the engine forbids calling internal methods with extra arguments, extensions that declare magic accessors (e.g. soap) will need to declare the new argument.
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
rfc/pass_scope_to_magic_accessors.txt · Last modified: 2023/05/09 16:25 by nicolasgrekas