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/16 11:43] – jgivoni | rfc:compact-object-property-assignment [2020/03/22 03:17] – 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.0 | + | * Version: 1.3 |
- | * Date: 2020-03-10 | + | * Date: 2020-03-17 |
* Author: Jakob Givoni < | * Author: Jakob Givoni < | ||
- | * Status: Under Discussion | + | * Status: Under [[https:// |
===== Introduction ===== | ===== Introduction ===== | ||
Line 14: | 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 | ||
+ | |||
+ | You may find that COPA is best suited when you don’t mind public properties, don’t require a constructor and is willing to compromise on magic enforcement of mandatory properties, since as [[https:// | ||
==== Example ==== | ==== Example ==== | ||
Line 23: | Line 27: | ||
<code php> | <code php> | ||
- | $myObj-> | + | $myObj = new Foo(); // 1. Create |
+ | |||
+ | $myObj-> | ||
$myObj-> | $myObj-> | ||
$myObj-> | $myObj-> | ||
+ | |||
+ | doTheFoo($myObj); | ||
</ | </ | ||
=== You will be able to do this: === | === You will be able to do this: === | ||
<code php> | <code php> | ||
- | $myObj->[ | + | doTheFoo((new Foo)->[ |
a = 1, | a = 1, | ||
b = 2, | b = 2, | ||
c = 3, | c = 3, | ||
- | ]; | + | ]); |
</ | </ | ||
- | 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 - and keep an open mind about the syntax; the options are limited these days :-)// | ||
==== 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 51: | Line 61: | ||
* **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 can be implemented soon, but won’t trip up the continuing, inspiring development of the awesome language that is PHP.// | ||
+ | |||
+ | //If you have ever wanted to create, populate and send an object inside a function call, COPA is your chance!// | ||
===== Proposal ===== | ===== Proposal ===== | ||
Line 56: | Line 70: | ||
==== 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 brief and feels good. | ||
==== Interpretation ==== | ==== Interpretation ==== | ||
Line 62: | Line 88: | ||
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 ==== | ||
+ | |||
+ | === Alternative to passive associative arrays === | ||
+ | |||
+ | <code php> | ||
+ | // Instead of this: | ||
+ | |||
+ | doSomething([ | ||
+ | ' | ||
+ | ' | ||
+ | ]); | ||
+ | |||
+ | // Use COPA: | ||
+ | |||
+ | class Options { | ||
+ | public $a; | ||
+ | public $b; | ||
+ | } | ||
+ | |||
+ | doSomething((new Options)-> | ||
+ | a = 1, // Parameter name and type checking | ||
+ | b = 2, | ||
+ | ]); | ||
+ | </ | ||
+ | //If you often create, populate and send the same families of data structure, declaring those structures and using COPA makes it a breeze.// | ||
=== DTOs - data transfer objects === | === DTOs - data transfer objects === | ||
Line 77: | Line 141: | ||
<code php> | <code php> | ||
class FooDto { | class FooDto { | ||
- | public string $mane; | + | public |
public int $padme = 1; // Optional, with default | public int $padme = 1; // Optional, with default | ||
- | public FooDto $hum; | + | public |
} | } | ||
Line 93: | Line 157: | ||
<code php> | <code php> | ||
- | doTheFoo((new FooDto())->[ // Constructing and populating inline | + | doTheFoo((new FooDto)-> |
mane = ' | mane = ' | ||
- | hum = (new FooDto())->[ // Even nested structures | + | hum = (new FooDto)-> |
mane = ' | mane = ' | ||
], | ], | ||
Line 136: | Line 200: | ||
<code php> | <code php> | ||
class FooOptions { // Separate concerns into an options class that handles optional and default values... | class FooOptions { // Separate concerns into an options class that handles optional and default values... | ||
- | public string $mane; | + | public |
public int $padme = 1; // Optional, with default | public int $padme = 1; // Optional, with default | ||
- | public string $hum; | + | public |
} | } | ||
Line 145: | Line 209: | ||
public function __construct(FooOptions $options) { | public function __construct(FooOptions $options) { | ||
+ | // Do some validate here if you must, f.ex. checking for mandatory parameters | ||
$this-> | $this-> | ||
} | } | ||
} | } | ||
- | $myFoo = new Foo((new FooOptions())->[ // Objects as argument bags (pseudo named parameters? | + | $myFoo = new Foo((new FooOptions)-> |
mane = ' | mane = ' | ||
hum = ' | hum = ' | ||
Line 175: | Line 240: | ||
//There may be arguments equally for and against this behavior, but ultimately the simplicity of the design and implementation wins, in my opinion.// | //There may be arguments equally for and against this behavior, but ultimately the simplicity of the design and implementation wins, in my opinion.// | ||
+ | |||
+ | === Exceptions === | ||
+ | |||
+ | If an expression inside a COPA block throws an exception, the result is the same as if the assignments had been done the old way, f.ex. if we have: | ||
+ | |||
+ | <code php> | ||
+ | class Foo { | ||
+ | public $a; | ||
+ | public $b; | ||
+ | public $c; | ||
+ | } | ||
+ | |||
+ | $foo = new Foo(); | ||
+ | |||
+ | function iThrow() { | ||
+ | throw new \Exception(); | ||
+ | } | ||
+ | </ | ||
+ | Then the following examples behave identically: | ||
+ | |||
+ | <code php> | ||
+ | // With COPA | ||
+ | try { | ||
+ | $foo->[ | ||
+ | a = ' | ||
+ | b = iThrow(), | ||
+ | c = ' | ||
+ | ]; | ||
+ | } catch (\Throwable $e) { | ||
+ | var_dump($foo); | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | <code php> | ||
+ | // Without COPA | ||
+ | try { | ||
+ | $foo-> | ||
+ | -> | ||
+ | -> | ||
+ | } catch (\Throwable $e) { | ||
+ | var_dump($foo); | ||
+ | } | ||
+ | |||
+ | // OR | ||
+ | |||
+ | try { | ||
+ | $foo->a = ' | ||
+ | $foo->b = iThrow(); | ||
+ | $foo->c = ' | ||
+ | } catch (\Throwable $e) { | ||
+ | var_dump($foo); | ||
+ | } | ||
+ | </ | ||
+ | The result in all cases is that '' | ||
+ | |||
+ | <code php> | ||
+ | object(Foo)# | ||
+ | [" | ||
+ | string(1) " | ||
+ | [" | ||
+ | NULL | ||
+ | [" | ||
+ | NULL | ||
+ | } | ||
+ | </ | ||
+ | //I.e. COPA is not an atomic operation in the same way method chaining isn’t.// | ||
==== Out of scope / future scope ==== | ==== Out of scope / future scope ==== | ||
Line 204: | Line 335: | ||
=== Nested COPA === | === Nested COPA === | ||
- | It might be nice to be able to populate | + | It might be nice to be able to populate |
<code php> | <code php> | ||
Line 218: | Line 349: | ||
], | ], | ||
]; | ]; | ||
+ | |||
+ | // 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 | + | None. Array followed by square bracket causes |
- | ==== Vanilla style population | + | ===== Proposed PHP Version(s) ===== |
- | <code php> | + | PHP 8.0 |
- | class Foo { | + | |
- | public int $bar; | + | |
- | public int $baz; | + | |
- | } | + | |
- | $foo = new Foo(); | + | ===== Open Issues ===== |
- | $foo-> | + | |
- | $foo-> | + | |
- | doTheFoo($foo); | + | ==== Alternative syntaxes ==== |
- | // Oh yeah? What if I only need to set a single property? | + | I’m going to suggest some alternative syntaxes, that we can vote on, provided their feasibility has been vetted by an experienced internals developer: |
- | doTheFoo((new Foo())-> | + | |
- | </ | + | |
- | ==== Applying a touch of magic ==== | + | |
- | <code php> | + | === Syntax A === |
- | /** | + | |
- | * @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 | + | This is the originally proposed one: |
- | public function __call(string $method, array $params): self { | + | |
- | if (strpos($method, | + | |
- | $name = substr($method, | + | |
- | $this-> | + | |
- | } | + | |
- | return $this; | + | |
- | } | + | |
- | } | + | |
- | doTheFoo((new Foo()) // Works, but requires boilerplate | + | <code php> |
- | ->setBar(1) | + | $foo->[ |
- | | + | a = 1, |
- | ); | + | |
+ | c = (new Foo)->[ | ||
+ | a = 3, | ||
+ | b = 4, | ||
+ | ], | ||
+ | ]; | ||
</ | </ | ||
- | ==== Anonymous classes have some tricks up their sleeves! ==== | + | === Syntax B === |
+ | |||
+ | Since the [[https:// | ||
<code php> | <code php> | ||
- | class Foo { | + | $foo{ |
- | | + | |
- | | + | |
- | | + | |
- | } | + | a = 3, |
+ | b = 4, | ||
+ | | ||
+ | }; | ||
+ | </ | ||
+ | === Syntax C === | ||
- | doTheFoo(new class extends Foo { | + | No wrapper: |
- | 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() { | + | <code php> |
- | // And if I need expressions, | + | $foo-> |
- | | + | a = 1, |
- | | + | b = 2, |
- | | + | c = (new Foo)-> |
- | }; | + | |
- | } | + | |
- | }); // Pretty ugly, I'm afraid... | + | ;, |
+ | ; | ||
</ | </ | ||
- | ==== Lambda expression ==== | + | Nesting becomes awkward - how do we jump out again? |
- | <code php> | + | === Syntax D === |
- | class Foo { | + | |
- | public int $bar; | + | |
- | public int $baz; | + | |
- | } | + | |
- | doTheFoo((function(){ | + | Repeating the array for familiarity: |
- | | + | |
- | $foo->bar = 1; | + | <code php> |
- | $foo->baz = 2; | + | $foo |
- | return $foo; | + | -> |
- | })()); // Pretty good, if you can get those brackets straight... until you need to use values from the outside scope :-( | + | ->b = 2, |
+ | ->c = (new Foo) | ||
+ | ->a = 3, | ||
+ | ->b = 4, | ||
+ | ;, | ||
+ | ; | ||
</ | </ | ||
- | ===== Anti-proposal ===== | + | Same issue with nested as previous. We need to find a way to express end of COPA block. |
- | 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: | + | === Syntax E === |
- | ==== COPA is NOT json ==== | + | Like the original but with normal brackets instead of square ones: |
- | This is not a way to write object literals using JavaScript Object Notation [[https:// | + | < |
+ | $foo->( | ||
+ | | ||
+ | b = 2, | ||
+ | c = (new Foo)-> | ||
+ | | ||
+ | b = 4, | ||
+ | ), | ||
+ | ); | ||
+ | </ | ||
+ | ==== Mandatory properties ==== | ||
- | ==== COPA is NOT object initializer ==== | + | Some criticism has been that COPA is of little use without also enforcing mandatory properties to be set. |
- | You could call it **// | + | Rowan Tommins: |
- | ==== COPA is NOT named parameters ==== | + | > It seems pretty rare that an object would have no mandatory properties, so saying “if you have a mandatory property, |
- | Though on the wish list since 2013, named parameters [[https:// | + | Michał Brzuchalski: |
- | ===== Backward Incompatible Changes ===== | + | > 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 |
- | None. Array followed by square bracket causes syntax error in PHP 7.4. This new syntax | + | 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 |
- | ===== Proposed PHP Version(s) ===== | + | For now you must continue to write your own validation code to be carried out at the appropriate “point of no return”. |
- | PHP 8.0 | + | ==== Atomic operations ==== |
- | ===== Open Issues ===== | + | It’s also been suggested that assigning multiple values using COPA should be an atomic operation that either succeeds or fails in its entirety. |
- | None so far. RFC still under discussion - any open questions coming up will be added here. | + | That does sound cool as well, and may seem like the expected behavior for some. |
+ | |||
+ | Still, I’m not convinved it’s worth the extra hassle, since what were you planning to do with the incomplete object anyway? | ||
+ | |||
+ | Chaining method calls are not an atomic operation and if an exception is thrown in the middle I doubt you would raise an eyebrow about the previous call having altered the state of the object. | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
Line 335: | Line 477: | ||
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, | + | A secondary “vote” directed at no-voters, |
+ | |||
+ | The options will be: | ||
+ | |||
+ | - 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 negatively limit future changes | ||
+ | - This will be a nightmare to implement and maintain | ||
+ | - I prefer not to say | ||
+ | |||
+ | This will help understand what the obstacles are, when studying this RFC in the future, should anyone be tempted to have another shot at object literals et. al. | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== |
rfc/compact-object-property-assignment.txt · Last modified: 2020/04/14 06:30 by jgivoni