rfc:compact-object-property-assignment
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:compact-object-property-assignment [2020/03/17 02:53] – jgivoni | rfc:compact-object-property-assignment [2020/03/31 02:16] – jgivoni | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Compact Object Property Assignment ====== | ====== PHP RFC: Compact Object Property Assignment ====== | ||
- | **COPA: A pragmatic approach to object literals** | + | **COPA: A //pragmatic// approach to object literals** |
- | * Version: 1.1 | + | * Version: 1.3 |
- | * Date: 2020-03-16 | + | * Date: 2020-03-17 |
* Author: Jakob Givoni < | * Author: Jakob Givoni < | ||
- | * Status: Under Discussion | + | * Status: Under [[https:// |
- | * Discussion: | + | |
===== Introduction ===== | ===== Introduction ===== | ||
Line 15: | Line 14: | ||
This RFC proposes a new, compact syntax to assign values to multiple properties on an object in a single expression. | This RFC proposes a new, compact syntax to assign values to multiple properties on an object in a single expression. | ||
- | This //**pseudo object literal**// notation, (though not limited to such use) is intended to enable the developer to create an object and populating it inline, | + | This //**pseudo object literal**// notation, (though not limited to such use) is intended to enable the developer to //**create an object and populating it inline**//, f.ex. directly as an argument in a function call. |
+ | |||
+ | As an alternative | ||
+ | |||
+ | > COPA does not introduce any new concepts or complexities, | ||
==== Example ==== | ==== Example ==== | ||
Line 37: | Line 40: | ||
]; | ]; | ||
</ | </ | ||
- | And that’s all there is to it - the rest follow from this, as you’ll see in the use cases below. | + | > COPA is everything |
+ | |||
+ | //See more use cases below.// | ||
==== Motivation ==== | ==== Motivation ==== | ||
- | The purpose of this feature is to lighten the effort of populating data structures, | + | The purpose of this feature is to lighten the effort of creating, |
- | Ideally the solution | + | The goal was to find a solution |
* **Brief** - only mention the object once (less repetition) | * **Brief** - only mention the object once (less repetition) | ||
Line 52: | Line 57: | ||
* **Sparcity** - any property can be “skipped” (“skipped” properties may acquire a default value) | * **Sparcity** - any property can be “skipped” (“skipped” properties may acquire a default value) | ||
* **Simple** - does what you would expect without introducing any new concepts into the language | * **Simple** - does what you would expect without introducing any new concepts into the language | ||
+ | |||
+ | > My focus is to find a **pragmatic** solution that is trivial to implement, and won’t impede futher development of the language. | ||
+ | |||
+ | //If you have ever wanted to create, populate and send an object inside a function call, COPA is for you!// | ||
===== Proposal ===== | ===== Proposal ===== | ||
Line 57: | Line 66: | ||
==== Syntax ==== | ==== Syntax ==== | ||
- | The proposed syntax | + | The proposed syntax |
+ | |||
+ | < | ||
+ | < | ||
+ | `< | ||
+ | [repeat as needed], | ||
+ | ] | ||
+ | </ | ||
+ | > A trailing comma is permitted for the same reasons it's permitted in array literals and [[https:// | ||
+ | |||
+ | The whole block is considered an expression that returns the object | ||
+ | |||
+ | This syntax was chosen for its availability in the language. If we land on another syntax, I’m not married to this one. The only criteria are that it doesn’t conflict with anything else, that it is trivial to implement, brief and feels ok. | ||
==== Interpretation ==== | ==== Interpretation ==== | ||
Line 63: | Line 84: | ||
Each comma-separated assignment inside the brackets is executed as an assignment of the named property on the object preceding the block. If the property is defined and publicly accessible, it will simply be set, or possible throw a '' | Each comma-separated assignment inside the brackets is executed as an assignment of the named property on the object preceding the block. If the property is defined and publicly accessible, it will simply be set, or possible throw a '' | ||
+ | If you replace COPA with single line assignments, | ||
+ | |||
+ | <code php> | ||
+ | $foo->[ | ||
+ | a = 1, | ||
+ | b = myfunc(), | ||
+ | c = $foo-> | ||
+ | ]; | ||
+ | |||
+ | // The COPA above is identical to | ||
+ | $foo->a = 1; | ||
+ | $foo->b = myfunc(); | ||
+ | $foo->c = $foo-> | ||
+ | </ | ||
==== Use cases ==== | ==== Use cases ==== | ||
- | === DTOs - data transfer objects | + | === Create and send struct |
- | Typical characteristics | + | <code php> |
+ | // Instead | ||
- | * many properties | + | $myObj = new Foo; // 1. Create struct-like object without constructor arguments |
- | * properties may be optional, with default values | + | |
- | * public visibility on properties, i.e. no desire for boilerplate code to create setters and getters for each one | + | |
- | * desirability to create, populate and send in one go | + | |
- | == With current syntax == | + | $myObj-> |
+ | $myObj-> | ||
+ | $myObj-> | ||
- | <code php> | + | doTheFoo($myObj); // 3. Send or process |
- | class FooDto { | + | |
- | public string | + | |
- | public int $padme = 1; // Optional, with default | + | |
- | public FooDto $hum; | + | |
- | } | + | |
- | $foo = new FooDto(); | + | // Use COPA: |
- | $foo-> | + | |
- | // Skipping the $padme property which has a default value | + | |
- | $foo-> | + | |
- | $foo-> | + | |
- | doTheFoo($foo); // Passing it to a function | + | doTheFoo((new Foo)->[ |
+ | | ||
+ | b = 2, | ||
+ | c = 3, | ||
+ | ]); | ||
</ | </ | ||
- | == With new COPA syntax | + | //No boilerplate needed.// |
+ | |||
+ | === Stop using arrays === | ||
<code php> | <code php> | ||
- | doTheFoo((new FooDto())-> | + | // Instead of this: |
- | | + | |
- | hum = (new FooDto())->[ // Even nested structures | + | doSomething([ |
- | mane = 'life', | + | 'a' => 1, // Anonymous array doesn' |
- | ], | + | 'b' |
]); | ]); | ||
- | </ | ||
- | //Though the example is not a typical DTO, it represents the characteristics.// | ||
- | === Argument bags === | + | // Use COPA: |
- | Argument bags are typically used when: | + | class Options { // Give the data a signature, a well-defined structure |
+ | public $a; | ||
+ | public $b; | ||
+ | } | ||
- | * many arguments needs to be passed to a function | + | doSomething((new Options)-> |
- | * some arguments are optional | + | |
- | * order of arguments is not important | + | b = 2, |
+ | ]); | ||
+ | </ | ||
+ | //If you often create, populate and send the same families | ||
- | With the proposed new syntax we can **avoid using simple arrays** and instead get **autocomplete** and **type-checking** in the IDE with a syntax that smells of **named parameters**: | + | === Nested COPA === |
- | == With current syntax == | + | COPA is not limited to a flat structure. |
<code php> | <code php> | ||
- | class Foo { | + | (new Foo)-> |
- | protected string $mane; | + | om => 'get', |
- | protected int $padme; | + | |
- | protected string $hum; | + | |
- | + | mane = 'life', | |
- | public function __construct(array $options) { | + | |
- | $this->mane = $options[' | + | ]; |
- | | + | |
- | | + | |
- | | + | |
- | } | + | |
- | + | ||
- | $myFoo | + | |
- | ' | + | |
- | | + | |
- | ]); | + | |
</ | </ | ||
- | == With new COPA syntax | + | === Split options from services |
+ | |||
+ | Separate concerns and use composition. In this example, once you have instantiated Foo, the options are no longer writeable, even though the options were public properties. | ||
<code php> | <code php> | ||
- | class FooOptions { // Separate concerns into an options class that handles optional and default values... | + | class FooOptions { |
- | public string $mane; | + | public |
public int $padme = 1; // Optional, with default | public int $padme = 1; // Optional, with default | ||
- | public string $hum; | + | public |
} | } | ||
- | class Foo { // And the main class that receives the options and handles some feature | + | class Foo { |
protected FooOptions $options; | protected FooOptions $options; | ||
public function __construct(FooOptions $options) { | public function __construct(FooOptions $options) { | ||
- | $this-> | + | |
+ | | ||
} | } | ||
} | } | ||
- | $myFoo = new Foo((new FooOptions())-> | + | $myFoo = new Foo((new FooOptions)-> |
- | mane = ' | + | mane = ' |
hum = ' | hum = ' | ||
]); | ]); | ||
</ | </ | ||
- | //The other alternative | + | //If you can’t wait for “named parameters” and often resort |
==== Special cases ==== | ==== Special cases ==== | ||
Line 173: | Line 203: | ||
var_dump($myObj-> | var_dump($myObj-> | ||
</ | </ | ||
- | //As the assignments are carried out in order on the object, you can use the new value of a previous assigment in a following one.// | + | > As the assignments are carried out in order on the object, you can use the new value of a previous assigment in a following one. |
- | + | ||
- | //There may be arguments equally for and against this behavior, but ultimately the simplicity of the design and implementation wins, in my opinion.// | + | |
=== Exceptions === | === Exceptions === | ||
Line 194: | Line 222: | ||
} | } | ||
</ | </ | ||
- | Then the following | + | Then the following |
<code php> | <code php> | ||
- | // With COPA | + | // With COPA: |
try { | try { | ||
$foo->[ | $foo->[ | ||
Line 210: | Line 239: | ||
</ | </ | ||
<code php> | <code php> | ||
- | // Without COPA | + | // Without COPA: |
+ | |||
+ | try { | ||
+ | $foo-> | ||
+ | -> | ||
+ | -> | ||
+ | } catch (\Throwable $e) { | ||
+ | var_dump($foo); | ||
+ | } | ||
+ | |||
+ | // OR | ||
try { | try { | ||
$foo->a = ' | $foo->a = ' | ||
Line 219: | Line 259: | ||
} | } | ||
</ | </ | ||
- | Result, | + | The result in all cases is that '' |
- | < | + | < |
object(Foo)# | object(Foo)# | ||
[" | [" | ||
Line 231: | Line 271: | ||
} | } | ||
</ | </ | ||
+ | > COPA is **not** an atomic operation in the same way that method chaining isn’t. | ||
+ | |||
==== Out of scope / future scope ==== | ==== Out of scope / future scope ==== | ||
- | This section contains | + | This section contains features that is not considered for implementation in version 1 of COPA but may be considered later. |
- | === Can you do that? === | + | === You can’t |
- | The following examples show some things that is now possible using regular property accessor, | + | The following examples show various |
<code php> | <code php> | ||
Line 255: | Line 297: | ||
]; | ]; | ||
</ | </ | ||
- | //If anyone | + | //These can be implemented in the future if there is a demand.// |
- | === Nested COPA === | + | === Nested COPA on existing objects |
- | It might be nice to be able to populate a nested object | + | The following syntax could be supported |
<code php> | <code php> | ||
// This example, using current syntax... | // This example, using current syntax... | ||
+ | |||
$foo->a = 1; | $foo->a = 1; | ||
$foo-> | $foo-> | ||
// Could be written with COPA like this: | // Could be written with COPA like this: | ||
+ | |||
$foo->[ | $foo->[ | ||
a = 1, | a = 1, | ||
Line 273: | Line 317: | ||
], | ], | ||
]; | ]; | ||
+ | |||
+ | // But for now you'll have to do this: | ||
+ | |||
+ | $foo->[ | ||
+ | a = 1, | ||
+ | b = $foo-> | ||
+ | c = 2, | ||
+ | ], | ||
+ | ]; | ||
+ | |||
</ | </ | ||
- | ===== Why don't you just... | + | ===== Backward Incompatible Changes |
- | What follows is a handful of alternative ways to populate an object with existing syntax, and some hints as to why they just doesn' | + | None. |
- | ==== Vanilla style population ==== | + | > **Note!** Array followed by square bracket causes syntax error in PHP 7.4. This new syntax is optional. If you don't use it, your code will continue to run. |
- | <code php> | + | ===== Proposed PHP Version(s) ===== |
- | class Foo { | + | |
- | public int $bar; | + | |
- | public int $baz; | + | |
- | } | + | |
- | $foo = new Foo(); | + | PHP 8.0 |
- | $foo-> | + | |
- | $foo-> | + | |
- | doTheFoo($foo); | + | ===== Open Issues ===== |
- | // Oh yeah? What if I only need to set a single property? | + | ==== Alternative syntaxes |
- | doTheFoo((new Foo())-> | + | |
- | </ | + | |
- | ==== Applying a touch of magic ==== | + | |
- | <code php> | + | I’m going to suggest some alternative syntaxes, which we can vote on, provided their feasibility has been vetted by an experienced internals developer: |
- | /** | + | |
- | * @method self setBar(int $bar) // Use annotations | + | |
- | * @method self setBaz(int $baz) // Duplicate the property signatures | + | |
- | */ | + | |
- | class Foo { | + | |
- | protected int $bar; | + | |
- | protected int $baz; | + | |
- | // This generic method could be injected using a trait | + | === Syntax A === |
- | public function __call(string $method, array $params): self { | + | |
- | if (strpos($method, | + | |
- | $name = substr($method, | + | |
- | $this-> | + | |
- | } | + | |
- | return $this; | + | |
- | } | + | |
- | } | + | |
- | doTheFoo((new Foo()) | + | This is the originally proposed one: |
- | ->setBar(1) | + | |
- | | + | <code php> |
- | ); | + | $foo->[ |
+ | a = 1, | ||
+ | | ||
+ | c = (new Foo)->[ | ||
+ | a = 3, | ||
+ | b = 4, | ||
+ | ], | ||
+ | ]; | ||
</ | </ | ||
- | //Works, but requires some boilerplate code// | + | === Syntax B === |
- | ==== Anonymous classes have some tricks up their sleeves! ==== | + | Since the [[https:// |
<code php> | <code php> | ||
- | class Foo { | + | $foo{ |
- | | + | |
- | | + | |
- | | + | |
- | } | + | a = 3, |
+ | b = 4, | ||
+ | | ||
+ | }; | ||
+ | </ | ||
+ | > Going from deprecation in 7.4 to removal of support in 8.0 may is not unprecedented. Old code that has not been mended won’t silently do something spurious. | ||
- | doTheFoo(new class extends Foo { | + | === Syntax C === |
- | public int $bar = 1; // Assigning values inline is now possible without creating setters! | + | |
- | public int $baz = 2; // But I have to repeat their signature, which is annoying | + | |
- | public function __construct() { | + | No wrapper: |
- | // And if I need expressions, | + | |
- | $this->sub = new class extends | + | <code php> |
- | | + | $foo-> |
- | | + | a = 1, |
- | }; | + | b = 2, |
- | } | + | c = (new Foo)-> |
- | }); | + | |
+ | | ||
+ | ;, | ||
+ | ; | ||
</ | </ | ||
- | //Pretty ugly, I’m afraid…// | + | Nesting becomes awkward - how do we jump out again? |
- | ==== Lambda expression ==== | + | > **Note!** This looks more like a chain of normal assignments, |
- | <code php> | + | === Syntax D === |
- | class Foo { | + | |
- | public int $bar; | + | |
- | public int $baz; | + | |
- | } | + | |
- | doTheFoo((function(){ | + | Repeating the arrow for familiarity with regular property assignment: |
- | | + | |
- | $foo->bar = 1; | + | <code php> |
- | $foo->baz = 2; | + | $foo |
- | return $foo; | + | -> |
- | })()); | + | ->b = 2, |
+ | ->c = (new Foo) | ||
+ | ->a = 3, | ||
+ | ->b = 4, | ||
+ | ;, | ||
+ | ; | ||
</ | </ | ||
- | //Pretty good, if you can get those brackets straight… until you need to use values from the outside scope :-(// | + | Same issues as previous. |
- | ===== Anti-proposal ===== | + | === Syntax E === |
- | This proposal is related to previous RFCs and shares motivation with them. However, though **COPA** claims to be in the same family, here are some disclaimers: | + | Like the original but with normal brackets instead of square ones: |
- | ==== COPA is NOT json ==== | + | <code php> |
+ | $foo-> | ||
+ | a = 1, | ||
+ | b = 2, | ||
+ | c = (new Foo)-> | ||
+ | a = 3, | ||
+ | b = 4, | ||
+ | ), | ||
+ | ); | ||
+ | </ | ||
+ | === Syntax F === | ||
- | This is not a way to write object literals using JavaScript Object Notation [[https:// | + | **WITH** syntax |
- | ==== COPA is NOT object initializer ==== | + | <code php> |
+ | myObj.with { | ||
+ | | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | If this is the preferred syntax it will require a new RFC. | ||
- | You could call it **// | + | ===== Rejected Features ===== |
- | ==== COPA is NOT named parameters ==== | + | Some suggested features have been rejected due to the fact that COPA aims to be pragmatic, with a trivial implementation and without introducing any new concepts to avoid a combinatorial explosion of complexities in the future. |
- | Though on the wish list since 2013, named parameters [[https:// | + | ==== Mandatory properties ==== |
- | ===== Backward Incompatible Changes ===== | + | Some have voiced criticism that COPA is of little use without also enforcing mandatory properties to be set. |
- | None. Array followed by square bracket causes syntax error in PHP 7.4. This new syntax is optional. If you don't use it, your code will continue to run. | + | **Rowan Tommins:** |
- | ===== Proposed PHP Version(s) ===== | + | > It seems pretty rare that an object would have no mandatory properties, so saying “if you have a mandatory property, COPA is not for you” is ruling out a lot of uses. |
- | PHP 8.0 | + | **Michał Brzuchalski: |
- | ===== Open Issues ===== | + | > This helps to avoid bugs where a property is added to the class but forgot to be assigned it a value in all cases where the class is instantiated and initialized |
- | ==== Alternative syntaxes ==== | + | Mandatory properties are typed properties without a default value. They are in the uninitialized state until they are assigned a value. It has been suggested that an exception should be thrown at the end of the constructor if any property is still uninitialized, |
- | Some alternative syntaxes for COPA has been suggested, but I’m not convinced they can be implemented without hassle: | + | > COPA won’t support this since COPA doesn’t introduce any new concepts or complexities. The lack of this feature is not a limitation of COPA when compared to current functionality. |
- | <code php> | + | //For now you must continue |
- | $foo = new Foo()[ | + | |
- | | + | |
- | | + | |
- | ]; | + | |
- | </code> | + | |
- | For some reason it’s not possible | + | |
- | <code php> | + | ==== Atomic operations ==== |
- | new Foo()-> | + | |
- | </ | + | |
- | It’s necessary to wrap the instantiation in brackets: | + | |
- | <code php> | + | It’s also been suggested that assigning multiple values using COPA should be an atomic operation that either succeeds or fails in its entirety |
- | (new Foo())-> | + | |
- | </ | + | |
- | Which is why I think it will be necessary in my proposal as well. | + | |
- | Furthermore, a variable or object directly followed by square brackets usually imply array access on it. That syntax would conflict | + | Though that sounds cool, this is an edge case that won’t have any significant impact. If you were planning to resume gracefully |
+ | > **Note!** Chaining method calls is not an atomic operation either. The cost/ | ||
- | ---- | + | ===== Vote ===== |
- | //Unless someone can convince me that it’s trivial to implement another syntax that looks even better, my stance is that the people who are going to vote no on this because they don’t find the feature useful are not gonna change their mind if the syntax changes, | + | Voting starts 2020-03-31 |
- | + | ||
- | ===== Proposed Voting Choices ===== | + | |
The primary vote of whether or not to accept this RFC requires a 2/3 majority. | The primary vote of whether or not to accept this RFC requires a 2/3 majority. | ||
- | There may be a secondary “vote” directed at no-voters, where you’ll be asked the primary | + | <doodle title=" |
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
+ | |||
+ | <doodle title=" | ||
+ | * I voted yes! | ||
+ | * I don’t find the feature useful | ||
+ | * I don’t like the syntax | ||
+ | * I prefer a more comprehensive solution to this problem | ||
+ | * I prefer a narrower solution to this problem | ||
+ | * This breaks backwards compatibility | ||
+ | * This will have negative implications | ||
+ | | ||
+ | * I prefer not to say | ||
+ | </ | ||
+ | |||
+ | <doodle title=" | ||
+ | * A (the proposed one) | ||
+ | * B | ||
+ | * C | ||
+ | * D | ||
+ | * E | ||
+ | * F | ||
+ | * Irrelevant | ||
+ | </ | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== |
rfc/compact-object-property-assignment.txt · Last modified: 2020/04/14 06:30 by jgivoni