rfc:property-capture
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:property-capture [2023/04/22 20:50] – imsop | rfc: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), | * Thanks: Nicolas Grekas (nicolasgrekas@php.net), | ||
Line 24: | Line 24: | ||
Rather than inventing new syntax and semantics for these, this proposal reuses existing object properties and constructor parameters, as follows: | Rather than inventing new syntax and semantics for these, this proposal reuses existing object properties and constructor parameters, as follows: | ||
- | - If a use clause is present, generate an empty constructor which will be added to the anonymous class. | + | - If a '' |
- | - For each captured variable in the use clause, add it as a parameter to the constructor. | + | - 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 use clause (see below). | + | - 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 '' | + | - Set the default visibility of the property to '' |
- In the constructor body, assign the captured variable to the corresponding property. | - In the constructor body, assign the captured variable to the corresponding property. | ||
- When creating an // | - When creating an // | ||
Line 35: | Line 35: | ||
===== Syntax ===== | ===== Syntax ===== | ||
- | A new "use clause" | + | A new //'' |
==== Basic Form ==== | ==== Basic Form ==== | ||
Line 152: | Line 152: | ||
echo $anon-> | 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 = []): void { | ||
+ | $context[$this-> | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ===== 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 ===== | ===== Restrictions ===== | ||
- | Because it generates both property declarations and a constructor, | + | The following new errors follow from the use of properties, rather than a new mechanism, to access |
* " | * " | ||
* " | * " | ||
+ | |||
+ | The following new errors follow from the current implementation' | ||
+ | |||
* " | * " | ||
* " | * " | ||
- | ==== 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 196: | Line 244: | ||
<code php> | <code php> | ||
- | # NOT supported in proposal | + | # NOT supported in current |
$anon = new class($b, $c) use($a) extends SomeOtherClass { | $anon = new class($b, $c) use($a) extends SomeOtherClass { | ||
public function __construct($b, | public function __construct($b, | ||
Line 214: | Line 262: | ||
==== Alternative 2: Automatically Calling Parent Constructor ==== | ==== Alternative 2: Automatically Calling Parent Constructor ==== | ||
- | Another | + | Another |
<code php> | <code php> | ||
- | # NOT supported in proposal | + | # NOT supported in current |
$anon = new class($foo) use($bar) extends SomeOtherClass {}; | $anon = new class($foo) use($bar) extends SomeOtherClass {}; | ||
</ | </ | ||
Line 225: | Line 273: | ||
<code php> | <code php> | ||
$anon = new class($foo, $bar) extends SomeOtherClass { | $anon = new class($foo, $bar) extends SomeOtherClass { | ||
- | | + | |
public function __construct($foo, | public function __construct($foo, | ||
$this-> | $this-> | ||
Line 244: | Line 292: | ||
==== Alternative 3: Generating a Different Method ==== | ==== Alternative 3: Generating a Different Method ==== | ||
- | ==== Alternative 4: Calling an Additional Magic Method ==== | + | Similar to the workaround above, the logic for setting captured property values could be generated in a method other than the constructor, |
- | ===== Reflection ===== | + | That is, compile < |
- | The constructor, | + | <code php> |
+ | $anon = new class { | ||
+ | var $foo; | ||
+ | public function __capture($foo) { | ||
+ | $this-> | ||
+ | } | ||
+ | }; | ||
+ | $anon-> | ||
+ | </ | ||
- | * '' | + | That would allow this: |
- | * '' | + | |
- | Although internally they are declared using Constructor Property Promotion, the parameters and properties return '' | + | <code php> |
+ | # NOT supported in current proposal | ||
+ | $anon = new class($b, $c) use($a) extends SomeOtherClass { | ||
+ | public function __construct($b, | ||
+ | do_something($b); | ||
+ | parent::__construct($c); | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
- | The generated | + | 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 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 | ||
+ | |||
+ | ==== 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 ===== | ||
Line 273: | Line 373: | ||
None anticipated, | None anticipated, | ||
+ | ===== Unaffected Functionality ===== | ||
+ | |||
+ | All existing features of anonymous classes are retained, and can be combined with the new '' | ||
+ | |||
+ | * Inheriting parent classes | ||
+ | * Implementing interfaces | ||
+ | * Using traits | ||
+ | * Implementing any method other than '' | ||
+ | * Declaring the entire class '' | ||
===== Future Scope ===== | ===== Future Scope ===== | ||
Line 278: | Line 387: | ||
==== Arbitrary Expressions ==== | ==== Arbitrary Expressions ==== | ||
- | ===== Proposed Voting Choices ===== | + | When a renamed property is indicated with the '' |
- | Include these so readers know where you are heading and can discuss | + | |
- | ===== Patches and Tests ===== | + | <code php> |
- | Links to any external patches and tests go here. | + | $anon = new class use (self::ID as $id, get_some_value() * 2 as private $something) {}; |
+ | </ | ||
- | If there is no patch, make it clear who will create a patch, or whether a volunteer | + | Which would be equivalent |
- | Make it clear if the patch is intended to be the final patch, or is just a prototype. | + | <code php> |
+ | $anon = new class(self:: | ||
+ | public function __construct( | ||
+ | public $id, | ||
+ | private $something | ||
+ | ) {} | ||
+ | } | ||
+ | </ | ||
- | For changes affecting | + | ==== Extension to Anonymous Functions ==== |
+ | |||
+ | Taking | ||
+ | |||
+ | <code php> | ||
+ | $callback = function() use (self::ID as $id, get_some_value() * 2 as $something) { | ||
+ | do_something($id, | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | ===== Proposed Voting Choices ===== | ||
+ | |||
+ | Add property capture to anonymous classes, with the syntax and semantics proposed, in PHP 8.3 (Yes / No, two-thirds majority required for acceptance) | ||
+ | |||
+ | ===== Patches and Tests ===== | ||
+ | |||
+ | https:// | ||
===== Implementation ===== | ===== Implementation ===== | ||
Line 301: | Line 433: | ||
===== 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