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/12 02:13] – jgivoni | rfc:compact-object-property-assignment [2020/03/31 02:45] – updated to voting. danack | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Compact Object Property Assignment ====== | ====== PHP RFC: Compact Object Property Assignment ====== | ||
- | | + | |
- | * Date: 2020-03-10 | + | **COPA: A // |
+ | |||
+ | | ||
+ | * Date: 2020-03-17 | ||
* Author: Jakob Givoni < | * Author: Jakob Givoni < | ||
- | * Status: | + | * Status: |
===== Introduction ===== | ===== Introduction ===== | ||
==== Summary ==== | ==== Summary ==== | ||
- | **A pragmatic approach to mimicking object literals.** | ||
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**//, f.ex. directly as an argument in a function call. | ||
+ | |||
+ | As an alternative to writing a data structure as an associative array, COPA gives the data a **// | ||
+ | |||
+ | > COPA does not introduce any new concepts or complexities, | ||
==== Example ==== | ==== Example ==== | ||
+ | |||
+ | Let’s start with an example that demonstrates the essence of COPA. | ||
=== Instead of doing this... === | === Instead of doing this... === | ||
+ | |||
<code php> | <code php> | ||
- | $myObj->prop_a | + | $myObj->a = 1; |
- | $myObj->prop_b | + | $myObj->b = 2; |
- | $myObj->prop_c | + | $myObj->c = 3; |
</ | </ | ||
- | |||
=== You will be able to do this: === | === You will be able to do this: === | ||
+ | |||
<code php> | <code php> | ||
$myObj-> | $myObj-> | ||
- | | + | |
- | | + | |
- | | + | |
]; | ]; | ||
</ | </ | ||
+ | > COPA is everything that comes after the object expression. You can use COPA on any expression that evaluates to an object. COPA is not tied to object construction, | ||
+ | |||
+ | //See more use cases below.// | ||
==== Motivation ==== | ==== Motivation ==== | ||
- | The purpose of this feature is to lighten the effort of populating data structures, especially medium to large ones. | ||
- | Ideally | + | The purpose of this feature is to lighten |
+ | |||
+ | The goal was to find a solution | ||
* **Brief** - only mention the object once (less repetition) | * **Brief** - only mention the object once (less repetition) | ||
Line 39: | Line 54: | ||
* **Typo-proof** - property names can be autocompleted easily by IDE (faster typing, fewer errors) | * **Typo-proof** - property names can be autocompleted easily by IDE (faster typing, fewer errors) | ||
* **Type-checking** - IDE can verify correct type for typed properties and annotated virtual properties | * **Type-checking** - IDE can verify correct type for typed properties and annotated virtual properties | ||
- | * **Order-agnostic** - properties can be specified in any order (this doesn' | + | * **Order-agnostic** - properties can be specified in any order (though note that the order //may// change |
- | * **Sparcity** - there' | + | * **Sparcity** - any property |
* **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 ===== | ||
==== Syntax ==== | ==== Syntax ==== | ||
- | The proposed syntax | + | |
- | A trailing comma is permitted for the same reasons it's permitted in array literals and function calls (as of PHP 7.3). | + | The proposed syntax |
- | The whole block is considered an expression that returns the object we started | + | |
+ | < | ||
+ | < | ||
+ | `< | ||
+ | | ||
+ | ] | ||
+ | </ | ||
+ | > 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 | ||
==== Interpretation ==== | ==== Interpretation ==== | ||
- | Each comma-separated assignment inside the curly 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 TypeError. If there' | ||
- | When used in an expression, COPA simply returns the object itself. | ||
- | ==== Use cases ==== | + | 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 '' |
- | === DTOs - data transfer objects === | + | If you replace COPA with single line assignments, you will always get the same result, f.ex.: |
- | Typical features of DTOs: | + | |
- | * many properties | + | |
- | * properties may be optional, | + | |
- | * public visibility on properties, no need for boilerplate code to create setters and getters for each one | + | |
- | == With current syntax == | ||
<code php> | <code php> | ||
- | class Dto { | + | $foo->[ |
- | public string | + | |
- | | + | |
- | | + | c = $foo->bar(), |
- | } | + | ]; |
- | + | ||
- | $myDto | + | |
- | $myDto->foo = ' | + | |
- | $myDto-> | + | |
- | myFunc($myDto); | + | // The COPA above is identical |
+ | $foo->a = 1; | ||
+ | $foo->b = myfunc(); | ||
+ | $foo->c = $foo-> | ||
</ | </ | ||
+ | ==== Use cases ==== | ||
+ | |||
+ | === Create and send struct === | ||
- | == With new syntax == | ||
<code php> | <code php> | ||
- | myFync((new Dto())-> | + | // Instead of this: |
- | | + | |
- | | + | $myObj = new Foo; // 1. Create struct-like object without constructor arguments |
+ | |||
+ | $myObj-> | ||
+ | $myObj-> | ||
+ | $myObj-> | ||
+ | |||
+ | doTheFoo($myObj); // 3. Send or process | ||
+ | |||
+ | // Use COPA: | ||
+ | |||
+ | doTheFoo((new Foo)->[ | ||
+ | | ||
+ | b = 2, | ||
+ | | ||
]); | ]); | ||
</ | </ | ||
+ | //No boilerplate needed.// | ||
- | === Argument bags === | + | === Stop using arrays |
- | Argument bags are typically used when: | + | |
- | * many arguments needs to be passed to a function | + | |
- | * some arguments are optional | + | |
- | * order of arguments is not important | + | |
- | + | ||
- | With the proposed COPA 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: | + | |
- | == With current syntax == | ||
<code php> | <code php> | ||
- | class Foo { | + | // Instead of this: |
- | protected string $foo; | + | |
- | protected int $bar; | + | |
- | protected string $baz; | + | |
- | public function __construct(array $options) { | + | doSomething([ |
- | | + | 'a' |
- | $this->bar = $options['bar' | + | |
- | $this->baz = $options[' | + | ]); |
- | | + | |
+ | // Use COPA: | ||
+ | |||
+ | class Options { // Give the data a signature, a well-defined structure | ||
+ | public | ||
+ | | ||
} | } | ||
- | $myFoo = new Foo([ | + | doSomething((new Options)-> |
- | | + | |
- | | + | |
]); | ]); | ||
</ | </ | ||
+ | //If you often create, populate and send the same families of data structure, declaring those structures and using COPA makes it a breeze.// | ||
- | == Alternatively, | + | === Nested COPA === |
- | <code php> | + | |
- | class Foo { | + | |
- | protected string $foo; | + | |
- | protected int $bar; | + | |
- | protected string $baz; | + | |
- | public function __construct(string $foo, int $bar, string $baz) { | + | COPA is not limited to a flat structure. |
- | $this-> | + | |
- | $this-> | + | |
- | $this-> | + | |
- | } | + | |
- | } | + | |
- | $myFoo = new Foo(' | + | <code php> |
+ | (new Foo)->[ | ||
+ | om => ' | ||
+ | mane = ' | ||
+ | hum = (new Foo)-> | ||
+ | mane = ' | ||
+ | ], | ||
+ | ]; | ||
</ | </ | ||
+ | === 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. | ||
- | == With new syntax == | ||
<code php> | <code php> | ||
- | class FooOptions { // Separate concerns into an options class that handles optional and default values... | + | class FooOptions { |
- | public string $foo; | + | public |
- | public int $bar = 1; // Optional, with default | + | public int $padme = 1; // Optional, with default |
- | public string $baz; | + | 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)-> |
- | | + | |
- | | + | |
]); | ]); | ||
</ | </ | ||
+ | //If you can’t wait for “named parameters” and often resort to “parameter bags” this is a perfectly valid and saner alternative.// | ||
==== Special cases ==== | ==== Special cases ==== | ||
+ | |||
+ | Clarification of edge-case behavior. | ||
=== Execution order === | === Execution order === | ||
- | The fact that the assignments are executed in the order they are listed, just as if they had been specified on separate lines, has the following consequence: | + | |
+ | The fact that the assignments are executed in the order they are listed | ||
<code php> | <code php> | ||
$myObj-> | $myObj-> | ||
Line 164: | 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. | ||
- | //There may be arguments equally for and against this behavior, but ultimately the simplicity of the design and implementation wins, in my opinion.// | + | === Exceptions === |
- | === Expressions | + | 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: |
- | Property names must be expressed literally. Some examples | + | |
+ | <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 | ||
+ | |||
+ | < | ||
+ | object(Foo)# | ||
+ | [" | ||
+ | string(1) " | ||
+ | [" | ||
+ | NULL | ||
+ | [" | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | > COPA is **not** an atomic operation in the same way that method chaining isn’t. | ||
+ | |||
+ | ==== Out of scope / future scope ==== | ||
+ | |||
+ | This section contains features that is not considered for implementation in version 1 of COPA but may be considered later. | ||
+ | |||
+ | === You can’t do that === | ||
+ | |||
+ | The following | ||
<code php> | <code php> | ||
$p = ' | $p = ' | ||
$myObj-> | $myObj-> | ||
- | $a-> | + | $a-> |
- | + | $a-> | |
+ | $a-> | ||
+ | $a->f++; // Increment/ | ||
$myObj-> | $myObj-> | ||
$p = ' | $p = ' | ||
{" | {" | ||
+ | b->c = ' | ||
+ | d[' | ||
+ | f++, // Syntax error | ||
]; | ]; | ||
</ | </ | ||
+ | //These can be implemented in the future if there is a demand.// | ||
- | // | + | === Nested COPA on existing objects === |
- | ===== Anti-proposal ===== | + | The following syntax could be supported |
- | This proposal is related to previous RFCs and shares motivation with them. However, there are distinctions and though COPA claims to be in the same family, here are some disclaimers: | + | |
- | ==== This is NOT json ==== | + | <code php> |
- | This is not a way to write object literals | + | // This example, |
- | * https:// | + | |
- | ==== This is NOT object initializer === | + | $foo-> |
- | I call this pseudo object literal notation because we're not writing the actual inner state of the object, we're merely populating properties after construction. But this syntax will allow you get benefits very similar to object literals in a simple, pragmatic way. | + | $foo->b->c = 2; |
- | * https:// | + | |
- | ==== This is NOT named parameters ==== | + | // Could be written |
- | Though on the wish list since 2013, named parameters have proved a tough nut to crack. But with this RFC you will be able to create parameter objects that may give you benefits very similar to named parameters when you pass it to a function that expects it. | + | |
- | * https:// | + | |
- | * https:// | + | |
+ | $foo->[ | ||
+ | a = 1, | ||
+ | b->[ | ||
+ | c = 2, | ||
+ | ], | ||
+ | ]; | ||
+ | |||
+ | // But for now you'll have to do this: | ||
+ | |||
+ | $foo->[ | ||
+ | a = 1, | ||
+ | b = $foo-> | ||
+ | c = 2, | ||
+ | ], | ||
+ | ]; | ||
+ | |||
+ | </ | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | 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. | + | None. |
+ | |||
+ | > **Note!** | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
- | Next PHP 8.x | + | |
+ | PHP 8.0 | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
- | ===== Proposed | + | ==== Alternative syntaxes |
+ | |||
+ | I’m going to suggest some alternative syntaxes, which we can vote on, provided their feasibility has been vetted by an experienced internals developer: | ||
+ | |||
+ | === Syntax A === | ||
+ | |||
+ | This is the originally proposed one: | ||
+ | |||
+ | <code php> | ||
+ | $foo-> | ||
+ | a = 1, | ||
+ | b = 2, | ||
+ | c = (new Foo)-> | ||
+ | a = 3, | ||
+ | b = 4, | ||
+ | ], | ||
+ | ]; | ||
+ | </ | ||
+ | === Syntax B === | ||
+ | |||
+ | Since the [[https:// | ||
+ | |||
+ | <code php> | ||
+ | $foo{ | ||
+ | a = 1, | ||
+ | b = 2, | ||
+ | c = (new 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. | ||
+ | |||
+ | === Syntax C === | ||
+ | |||
+ | No wrapper: | ||
+ | |||
+ | <code php> | ||
+ | $foo-> | ||
+ | a = 1, | ||
+ | b = 2, | ||
+ | c = (new Foo)-> | ||
+ | a = 3, | ||
+ | b = 4, | ||
+ | ;, | ||
+ | ; | ||
+ | </ | ||
+ | Nesting becomes awkward - how do we jump out again? | ||
+ | |||
+ | > **Note!** This looks more like a chain of normal assignments, | ||
+ | |||
+ | === Syntax D === | ||
+ | |||
+ | Repeating the arrow for familiarity with regular property assignment: | ||
+ | |||
+ | <code php> | ||
+ | $foo | ||
+ | ->a = 1, | ||
+ | ->b = 2, | ||
+ | ->c = (new Foo) | ||
+ | ->a = 3, | ||
+ | ->b = 4, | ||
+ | ;, | ||
+ | ; | ||
+ | </ | ||
+ | Same issues as previous. | ||
+ | |||
+ | === Syntax E === | ||
+ | |||
+ | Like the original but with normal brackets instead of square ones: | ||
+ | |||
+ | <code php> | ||
+ | $foo-> | ||
+ | a = 1, | ||
+ | b = 2, | ||
+ | c = (new Foo)-> | ||
+ | a = 3, | ||
+ | b = 4, | ||
+ | ), | ||
+ | ); | ||
+ | </ | ||
+ | === Syntax F === | ||
+ | |||
+ | **WITH** syntax | ||
+ | |||
+ | <code php> | ||
+ | myObj.with { | ||
+ | foo = 10 | ||
+ | bar = foo + 20 | ||
+ | } | ||
+ | </ | ||
+ | If this is the preferred syntax it will require a new RFC. | ||
+ | |||
+ | ===== Rejected Features ===== | ||
+ | |||
+ | 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. | ||
+ | |||
+ | ==== Mandatory properties ==== | ||
+ | |||
+ | Some have voiced criticism that COPA is of little use without also enforcing mandatory properties to be set. | ||
+ | |||
+ | **Rowan Tommins: | ||
+ | |||
+ | > 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. | ||
+ | |||
+ | **Michał Brzuchalski: | ||
+ | |||
+ | > 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 | ||
+ | |||
+ | 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, | ||
+ | |||
+ | > 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. | ||
+ | |||
+ | //For now you must continue to write your own validation code to be carried out at the appropriate “point of no return”.// | ||
+ | |||
+ | ==== Atomic operations ==== | ||
+ | |||
+ | It’s also been suggested that assigning multiple values using COPA should be an atomic operation that either succeeds or fails in its entirety (i.e. like a “transaction”). | ||
+ | |||
+ | Though that sounds cool, this is an edge case that won’t have any significant impact. If you were planning to resume gracefully with an incomplete object you should probably reconsider your goals in life. | ||
+ | |||
+ | > **Note!** Chaining method calls is not an atomic operation either. The cost/ | ||
+ | |||
+ | ===== Vote ===== | ||
+ | |||
+ | Voting | ||
+ | |||
+ | The primary vote of whether or not to accept this RFC requires a 2/3 majority. | ||
+ | |||
+ | <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 for future language evolution | ||
+ | * This will be a nightmare to implement and maintain | ||
+ | * I prefer not to say | ||
+ | </ | ||
+ | |||
+ | <doodle title="If you did not like the proposed syntax, which alternative would you prefer?" | ||
+ | * A (the proposed one) | ||
+ | * B | ||
+ | * C | ||
+ | * D | ||
+ | * E | ||
+ | * F | ||
+ | * Irrelevant | ||
+ | </ | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
+ | |||
There are yet no patches nor tests. The question of who will be developing this will be addressed if the RFC passes. | There are yet no patches nor tests. The question of who will be developing this will be addressed if the RFC passes. | ||
===== Implementation ===== | ===== Implementation ===== | ||
- | After the project is implemented, | + | |
+ | After the project is implemented, | ||
- the version(s) it was merged into | - the version(s) it was merged into | ||
- a link to the git commit(s) | - a link to the git commit(s) | ||
Line 221: | Line 511: | ||
===== References ===== | ===== References ===== | ||
+ | |||
+ | Related RFCs: | ||
+ | |||
* https:// | * https:// | ||
* https:// | * https:// | ||
* https:// | * https:// | ||
* https:// | * https:// | ||
- | + | * https:// | |
- | ===== Rejected Features ===== | + | * https:// |
+ | * https:// | ||
+ | * https:// | ||
rfc/compact-object-property-assignment.txt · Last modified: 2020/04/14 06:30 by jgivoni