rfc:property-capture

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
rfc:property-capture [2023/04/22 21:16] imsoprfc:property-capture [2023/04/23 21:15] (current) imsop
Line 1: Line 1:
 ====== PHP RFC: Property Capture for Anonymous Classes ====== ====== PHP RFC: Property Capture for Anonymous Classes ======
-  * Version: 0.2 +  * Version: 0.3 
-  * Date: 2023-04-22+  * Date: 2023-04-23
   * Author: Rowan Tommins (imsop@php.net)   * Author: Rowan Tommins (imsop@php.net)
   * Thanks: Nicolas Grekas (nicolasgrekas@php.net), Ilija Tovilo (tovilo.ilija@gmail.com)   * Thanks: Nicolas Grekas (nicolasgrekas@php.net), Ilija Tovilo (tovilo.ilija@gmail.com)
Line 35: Line 35:
 ===== Syntax ===== ===== Syntax =====
  
-A new //''use'' clause// is introduced, immediately after the keywords "new class", with the syntax ''use (<var-name> as <modifiers> <type> <property-name>, ...)''. The ''<modifiers>'', ''<type>'', and ''<property-name>'' are all optional; if none is specified, the "as" keyword must be omitted.+A new //''use'' clause// is introduced, immediately after the keywords "new class", with the syntax ''use ([&]<var-name> as <modifiers> <type> <property-name>, ...)''. The ''<modifiers>'', ''<type>'', and ''<property-name>'' are all optional; if none is specified, the "as" keyword must be omitted.
  
 ==== Basic Form ==== ==== Basic Form ====
Line 155: Line 155:
 ===== Examples ===== ===== Examples =====
  
-**TODO**+**TODO - expand** 
 + 
 +Create a struct-like object with readonly public properties: 
 + 
 +<code php> 
 +$id = get_next_id(); 
 +$name = get_name(); 
 +$user = new readonly class use ($id, $name) {}; 
 +echo "{$user->id}: {$user->name}"; 
 +$user->id = 42; // ERROR: Cannot modify readonly property $id 
 +</code> 
 + 
 +Decorate a [[https://www.php-fig.org/psr/psr-3/|PSR-3]] logger, adding some context to all entries logged: 
 + 
 +<code php> 
 +use Psr\Log\{LoggerInterface,LoggerTrait}; 
 + 
 +function decorate_logger(LoggerInterface $logger, string $contextKey, mixed $contextValue): LoggerInterface { 
 +   return new class  
 +        use ($logger as private $innerLogger, $contextKey as private, $contextValue as private)  
 +        implements LoggerInterface 
 +   { 
 +        public function log($level, string|\Stringable $message, array $context = []): void { 
 +            $context[$this->contextKey] = $this->contextValue; 
 +            $this->innerLogger->log($level, $message, $context); 
 +        } 
 +   }; 
 +
 +</code>
  
 ===== Reflection ===== ===== Reflection =====
Line 170: Line 198:
 ===== Restrictions ===== ===== Restrictions =====
  
-Because it generates both property declarations and constructor, the new syntax has a few restrictionswhich are indicated with new Errors thrown by the compiler:+The following new errors follow from the use of properties, rather than a new mechanismto access the captured values:
  
   * "Redefinition of captured property", e.g. <php>new class use($foo, $foo) {}</php> or <php>new class use($foo as $a, $bar as $a) {}</php>   * "Redefinition of captured property", e.g. <php>new class use($foo, $foo) {}</php> or <php>new class use($foo as $a, $bar as $a) {}</php>
   * "Captured property $a conflicts with existing property", e.g. <php>new class use($foo) { public $foo; }</php>   * "Captured property $a conflicts with existing property", e.g. <php>new class use($foo) { public $foo; }</php>
 +
 +The following new errors follow from the current implementation's use of a generated constructor:
 +
   * "Cannot declare custom constructor for anonymous class with captured properties", e.g. <php>new class use($foo) { public function __construct() {} }</php>   * "Cannot declare custom constructor for anonymous class with captured properties", e.g. <php>new class use($foo) { public function __construct() {} }</php>
   * "Cannot pass constructor arguments to anonymous class with captured properties", e.g. <php>new class($foo) use($bar) {}</php>   * "Cannot pass constructor arguments to anonymous class with captured properties", e.g. <php>new class($foo) use($bar) {}</php>
  
-==== Workarounds ====+Various alternatives to this restriction exist, discussed below. Throwing an Error now does not rule out any of these alternatives being implemented in future versions. 
 + 
 +==== Current Workaround ====
  
 The restrictions on custom constructors can be worked around by adding a normal instance method, and calling it immediately. The restrictions on custom constructors can be worked around by adding a normal instance method, and calling it immediately.
Line 229: Line 262:
 ==== Alternative 2: Automatically Calling Parent Constructor ==== ==== Alternative 2: Automatically Calling Parent Constructor ====
  
-Another suggestion is that any parameters passed to the ''new class'' statement could be automatically passed to the parent constructor; so this:+Another possibility is that any parameters passed to the ''new class'' statement could be automatically passed to the parent constructor; so this:
  
 <code php> <code php>
Line 302: Line 335:
  
 The main complexity here is generating the additional method call - in the above example, it is shown as called on the local variable ''$anon'', but in practice, it could happen anywhere in an expression, e.g. <php>some_function(new class($a) use($b) { ... });</php>. The main complexity here is generating the additional method call - in the above example, it is shown as called on the local variable ''$anon'', but in practice, it could happen anywhere in an expression, e.g. <php>some_function(new class($a) use($b) { ... });</php>.
 +
 +==== Alternative 3b: Initialising Before the Constructor Call ====
 +
 +A variation on the above would be to inject the extra method call (or the assignments themselves) immediately //before// the constructor is called.
 +
 +Although it would give slightly nicer semantics, this would likely be even more challenging to implement, since the object creation and constructor call are both part of the ''NEW'' opcode handler, so the additional logic would need to be added there, or in variant with a new opcode.
  
 ==== Alternative 4: Calling an Additional Magic Method ==== ==== Alternative 4: Calling an Additional Magic Method ====
Line 348: Line 387:
 ==== Arbitrary Expressions ==== ==== Arbitrary Expressions ====
  
-**TODO**+When a renamed property is indicated with the ''$variable as $property'' syntax, there is no technical need to name a local variable, rather than an arbitrary expression. In other words, it would be possible to allow this: 
 + 
 +<code php> 
 +$anon = new class use (self::ID as $id, get_some_value() 2 as private $something) {}; 
 +</code> 
 + 
 +Which would be equivalent to this: 
 + 
 +<code php> 
 +$anon = new class(self::ID, get_some_value() 2) { 
 +    public function __construct( 
 +        public $id, 
 +        private $something 
 +    ) {} 
 +
 +</code>
  
 ==== Extension to Anonymous Functions ==== ==== Extension to Anonymous Functions ====
  
-**TODO**+Taking the above a step further, the ''use'' clause on anonymous functions could be extended in the same way, producing locally-scoped variables based on the captured values: 
 + 
 +<code php> 
 +$callback = function() use (self::ID as $id, get_some_value() 2 as $something) { 
 +    do_something($id, $something); 
 +}; 
 +</code>
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
Line 360: Line 420:
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-**TODO**+https://github.com/php/php-src/pull/11123
  
 ===== Implementation ===== ===== Implementation =====
rfc/property-capture.1682198180.txt.gz · Last modified: 2023/04/22 21:16 by imsop