rfc:property-capture
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:property-capture [2023/04/15 20:28] – created imsop | rfc:property-capture [2023/04/23 19:53] – imsop | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Your Title Here ====== | + | ====== PHP RFC: Property Capture for Anonymous Classes |
- | * Version: 0.1 | + | * Version: 0.2 |
- | * Date: 2023-04-15 | + | * Date: 2023-04-22 |
* Author: Rowan Tommins (imsop@php.net) | * Author: Rowan Tommins (imsop@php.net) | ||
* Thanks: Nicolas Grekas (nicolasgrekas@php.net), | * Thanks: Nicolas Grekas (nicolasgrekas@php.net), | ||
Line 8: | Line 8: | ||
===== Introduction ===== | ===== Introduction ===== | ||
+ | |||
+ | This RFC proposes the addition of an inline syntax for lexical (captured) variables when declaring anonymous classes in PHP. The goal is to simplify anonymous class declarations and make them more concise by allowing developers to capture variables from the outer scope directly. | ||
Anonymous classes were introduced in PHP 7.0, using a syntax that allows declaring a class definition and constructing an instance in a single statement. However, unlike anonymous functions, it is not easy to " | Anonymous classes were introduced in PHP 7.0, using a syntax that allows declaring a class definition and constructing an instance in a single statement. However, unlike anonymous functions, it is not easy to " | ||
This RFC proposes a " | This RFC proposes a " | ||
+ | |||
+ | ===== Semantics ===== | ||
+ | |||
+ | Capturing variables into an anonymous class instance requires two things: | ||
+ | |||
+ | - A way to refer to the captured variables inside the //class definition//, | ||
+ | - A way to associate values with these variables for //a particular instance//, which will vary each time the statement is run | ||
+ | |||
+ | Rather than inventing new syntax and semantics for these, this proposal reuses existing object properties and constructor parameters, as follows: | ||
+ | |||
+ | - If a '' | ||
+ | - For each captured variable in the '' | ||
+ | - Declare a property with the same name as the captured variable in the anonymous class, unless renamed in the '' | ||
+ | - Set the default visibility of the property to '' | ||
+ | - In the constructor body, assign the captured variable to the corresponding property. | ||
+ | - When creating an // | ||
+ | |||
+ | The implementation reuses a lot of mechanics from the existing Constructor Property Promotion mechanism, but this is considered an implementation detail, not a functional guarantee. | ||
===== Syntax ===== | ===== Syntax ===== | ||
- | A new "use clause" | + | A new //'' |
==== Basic Form ==== | ==== Basic Form ==== | ||
Line 33: | Line 53: | ||
$bar = 2; | $bar = 2; | ||
$anon = new class($foo, $bar) { | $anon = new class($foo, $bar) { | ||
- | | + | |
- | | + | |
+ | | ||
public function __construct($foo, | public function __construct($foo, | ||
$this-> | $this-> | ||
Line 41: | Line 62: | ||
}; | }; | ||
</ | </ | ||
- | |||
- | ==== Visibility Modifiers ==== | ||
==== Renaming ==== | ==== Renaming ==== | ||
- | ===== Restrictions | + | By default, the property takes the same name as the outer variable, but this can be over-ridden using the syntax '' |
+ | |||
+ | <code php> | ||
+ | $foo = 1; | ||
+ | $bar = 2; | ||
+ | $anon = new class use ($foo as $one, $bar as $two, $bar as $three) {}; | ||
+ | </ | ||
+ | |||
+ | Is equivalent to: | ||
+ | |||
+ | <code php> | ||
+ | $foo = 1; | ||
+ | $bar = 2; | ||
+ | $anon = new class($foo, $bar, $bar) { | ||
+ | var $one; | ||
+ | var $two; | ||
+ | vat $three; | ||
+ | |||
+ | public function __construct($one, | ||
+ | $this-> | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | ==== Modifiers | ||
+ | |||
+ | The '' | ||
+ | |||
+ | The modifiers allowed are the same as for Constructor Property Promotion, which is used internally to declare the properties; that is currently: | ||
+ | |||
+ | * One of '' | ||
+ | * Optional '' | ||
+ | * A type specification | ||
+ | |||
+ | For example: | ||
+ | |||
+ | <code php> | ||
+ | $foo = 1; | ||
+ | $bar = 2; | ||
+ | $anon = new class use ($foo as private, $bar as protected readonly int, $bar as ?int $alsoBar) {}; | ||
+ | </ | ||
+ | |||
+ | Is equivalent to: | ||
+ | |||
+ | <code php> | ||
+ | $foo = 1; | ||
+ | $bar = 2; | ||
+ | $anon = new class($foo, $bar, $bar) { | ||
+ | private $foo; | ||
+ | protected readonly int $bar; | ||
+ | var ?int $alsoBar; | ||
+ | |||
+ | public function __construct($foo, | ||
+ | $this-> | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | ==== Capture by Reference ==== | ||
+ | |||
+ | It is possible to capture a variable by reference, by prefixing it with < | ||
+ | |||
+ | <code php> | ||
+ | $foo = 1; | ||
+ | $anon = new class use (&$foo as $fooProp) {}; | ||
+ | $foo = 2; | ||
+ | echo $anon-> | ||
+ | </ | ||
+ | |||
+ | Will print < | ||
+ | |||
+ | <code php> | ||
+ | $foo = 1; | ||
+ | $anon = new class($foo) { | ||
+ | var $one; | ||
+ | |||
+ | public function __construct(& | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | $foo = 2; | ||
+ | echo $anon-> | ||
+ | </ | ||
+ | |||
+ | ===== Examples ===== | ||
+ | |||
+ | **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-> | ||
+ | </ | ||
+ | |||
+ | Decorate a [[https:// | ||
+ | |||
+ | <code php> | ||
+ | use Psr\Log\{LoggerInterface, | ||
+ | |||
+ | function decorate_logger(LoggerInterface $logger, string $contextKey, | ||
+ | | ||
+ | use ($logger as private $innerLogger, | ||
+ | implements LoggerInterface | ||
+ | { | ||
+ | public function log($level, string|\Stringable $message, array $context | ||
+ | $context[$this-> | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | </ | ||
===== Reflection ===== | ===== Reflection ===== | ||
+ | |||
+ | The constructor, | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | Although internally they are declared using Constructor Property Promotion, the parameters and properties return '' | ||
+ | |||
+ | The generated constructor itself is not marked, partly due to implementation concerns that a limited number of bits remain available in the '' | ||
+ | |||
+ | ===== Restrictions ===== | ||
+ | |||
+ | Because it generates both property declarations and a constructor, | ||
+ | |||
+ | * " | ||
+ | * " | ||
+ | * " | ||
+ | * " | ||
+ | |||
+ | ==== Workarounds ==== | ||
+ | |||
+ | The restrictions on custom constructors can be worked around by adding a normal instance method, and calling it immediately. | ||
+ | |||
+ | That is, given a current definition like this: | ||
+ | |||
+ | <code php> | ||
+ | $anon = new class($a, $b, $c) extends SomeOtherClass { | ||
+ | private $a; | ||
+ | public function __construct($a, | ||
+ | $this->a = $a; | ||
+ | do_something($b); | ||
+ | parent:: | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | You could instead write this: | ||
+ | |||
+ | <code php> | ||
+ | $anon = new class use($a) extends SomeOtherClass { | ||
+ | public function init($b, $c) { | ||
+ | do_something($b); | ||
+ | parent:: | ||
+ | } | ||
+ | }; | ||
+ | $anon-> | ||
+ | </ | ||
+ | |||
+ | ==== Alternative 1: Merging Constructors ==== | ||
+ | |||
+ | It would be possible in principle to detect an existing constructor, | ||
+ | |||
+ | <code php> | ||
+ | # NOT supported in current proposal | ||
+ | $anon = new class($b, $c) use($a) extends SomeOtherClass { | ||
+ | public function __construct($b, | ||
+ | do_something($b); | ||
+ | parent:: | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | The principle difficulty here is finding a point in the compilation process where the explicit constructor can be easily detected but still modified. | ||
+ | |||
+ | It also leads to additional error conditions, which would give confusing errors if not specifically checked for: | ||
+ | |||
+ | * If too few or too many parameters are passed in the '' | ||
+ | * If the captured properties are added to the end of the parameter list, constructor parameters with default values would be disallowed. | ||
+ | |||
+ | ==== Alternative 2: Automatically Calling Parent Constructor ==== | ||
+ | |||
+ | Another suggestion is that any parameters passed to the '' | ||
+ | |||
+ | <code php> | ||
+ | # NOT supported in current proposal | ||
+ | $anon = new class($foo) use($bar) extends SomeOtherClass {}; | ||
+ | </ | ||
+ | |||
+ | would be equivalent to this: | ||
+ | |||
+ | <code php> | ||
+ | $anon = new class($foo, $bar) extends SomeOtherClass { | ||
+ | var $bar; | ||
+ | public function __construct($foo, | ||
+ | $this-> | ||
+ | parent:: | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | This seems to avoid the need to look up and manipulate the existing constructor definition, but discovering a parent constructor is actually even more difficult, as inheritance is only resolved after compilation. That leads to a few difficulties: | ||
+ | |||
+ | * It would be possible for the parent class to have a constructor with an incompatible signature, or no constructor at all | ||
+ | * The above example uses the input '' | ||
+ | |||
+ | Again, this leads to new error conditions which may be hard to understand to a user who doesn' | ||
+ | |||
+ | It would also not be very consistent with the rest of the language, which neither generates nor requires calls to parent constructors. | ||
+ | |||
+ | ==== Alternative 3: Generating a Different Method ==== | ||
+ | |||
+ | Similar to the workaround above, the logic for setting captured property values could be generated in a method other than the constructor, | ||
+ | |||
+ | That is, compile < | ||
+ | |||
+ | <code php> | ||
+ | $anon = new class { | ||
+ | var $foo; | ||
+ | public function __capture($foo) { | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | $anon-> | ||
+ | </ | ||
+ | |||
+ | That would allow this: | ||
+ | |||
+ | <code php> | ||
+ | # NOT supported in current proposal | ||
+ | $anon = new class($b, $c) use($a) extends SomeOtherClass { | ||
+ | public function __construct($b, | ||
+ | do_something($b); | ||
+ | parent:: | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | To be equivalent to this: | ||
+ | |||
+ | <code php> | ||
+ | $anon = new class($b, $c) extends SomeOtherClass { | ||
+ | var $a; | ||
+ | public function __construct($b, | ||
+ | do_something($b); | ||
+ | parent:: | ||
+ | } | ||
+ | public function __capture($a) { | ||
+ | $this->a = $a; | ||
+ | } | ||
+ | }; | ||
+ | $anon-> | ||
+ | </ | ||
+ | |||
+ | The main complexity here is generating the additional method call - in the above example, it is shown as called on the local variable '' | ||
+ | |||
+ | ==== Alternative 4: Calling an Additional Magic Method ==== | ||
+ | |||
+ | Another variation would be to have the constructor generated as in the current implementation, | ||
+ | |||
+ | <code php> | ||
+ | public function __construct($foo) { | ||
+ | $this-> | ||
+ | if ( method_exists($this, | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | If arguments are not supported, the advantage of this over existing workarounds is very slight. If they are supported, it would run into many of the same difficulties outlined in previous sections. | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | What breaks, and what is the justification for it? | + | |
+ | None. The new syntax is not currently valid PHP, and the behaviour of existing anonymous class declarations is unchanged. | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 58: | Line 355: | ||
===== RFC Impact ===== | ===== RFC Impact ===== | ||
- | ==== To SAPIs ==== | ||
- | Describe the impact to CLI, Development web server, embedded PHP etc. | ||
- | |||
==== To Existing Extensions ==== | ==== To Existing Extensions ==== | ||
- | Will existing extensions be affected? | + | |
+ | Extensions manipulating the AST may encounter the new node kinds '' | ||
==== To Opcache ==== | ==== To Opcache ==== | ||
- | It is necessary to develop RFC's with opcache in mind, since opcache is a core extension distributed with PHP. | + | None anticipated, but expert review on this point would be welcomed. |
- | Please explain how you have verified your RFC's compatibility with opcache. | + | ===== Unaffected Functionality ===== |
- | ==== New Constants ==== | + | All existing features of anonymous classes are retained, and can be combined with the new '' |
- | Describe any new constants so they can be accurately and comprehensively explained in the PHP documentation. | + | |
- | ==== php.ini Defaults ==== | + | * Inheriting parent classes |
- | If there are any php.ini settings then list: | + | * Implementing interfaces |
- | * hardcoded default values | + | * Using traits |
- | * php.ini-development values | + | * Implementing any method other than '' |
- | * php.ini-production values | + | * Declaring the entire class '' |
- | ===== Open Issues | + | ===== Future Scope ===== |
- | Make sure there are no open issues when the vote starts! | + | |
- | ===== Unaffected PHP Functionality | + | ==== Arbitrary Expressions |
- | List existing areas/ | + | |
- | This helps avoid any ambiguity, shows that you have thought deeply about the RFC's impact, and helps reduces mail list noise. | + | **TODO** |
- | ===== Future Scope ===== | + | ==== Extension to Anonymous Functions |
- | ==== Constructor Support ==== | + | **TODO** |
- | + | ||
- | ==== Arbitrary Expressions ==== | + | |
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | Include these so readers know where you are heading and can discuss | + | |
+ | Add property capture to anonymous classes, with the syntax and semantics | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | Links to any external patches and tests go here. | ||
- | |||
- | If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed. | ||
- | |||
- | Make it clear if the patch is intended to be the final patch, or is just a prototype. | ||
- | For changes affecting the core language, you should also provide a patch for the language specification. | + | https:// |
===== Implementation ===== | ===== Implementation ===== | ||
Line 115: | Line 401: | ||
===== Rejected Features ===== | ===== Rejected Features ===== | ||
- | Keep this updated with features that were discussed on the mail lists. | + |
rfc/property-capture.txt · Last modified: 2023/04/23 21:15 by imsop