On Tue, May 5, 2020 at 10:27 PM Larry Garfield <la...@garfieldtech.com> wrote:
> On Tue, May 5, 2020, at 7:35 AM, Nikita Popov wrote: > > > > Performing validation when the getAttributes() call is performed does > sound > > reasonable to me. We can also add a class flag to perform this validation > > only once (if it is successful), so the cost doesn't have to be paid by > > every single getAttributes() consumer. > > > > For the purpose of this RFC, I've now updated it to say that attributes > > will be applied to both properties and parameters ( > > https://wiki.php.net/rfc/constructor_promotion#attributes), but with an > > explicit note that we should change the behavior prior to the PHP 8 > release > > if it turns out to be problematic, e.g. with regards to an attribute > > validation feature. I think this is something of a detail, and we'll be > > mostly fine whichever way we chose, but it's hard to say right now which > > option is best, in particular with regard to future attributes > improvements. > > > > Regards, > > Nikita > > Thinking on it further, there's something sitting in the back of my head > that's bothering me. This helped me flesh it out. > > I like promotion, and think it will be great for simplifying code. > However, as attributes show as we load more bells and whistles onto > properties (even if they are desirable and useful bells and whistles) the > level of conflict is going to get larger. > > Consider, today we have: > > class Point { > protected int $x; > protected int $y; > > public function __construct(int $x, int $y) { > $this->x = $x; > $this->y = $y; > } > } > > And this is needlessly verbose. With promotion, that simplifies to: > > class Point { > public function __construct(protected int $x, protected int $y) {} > } > > And this is good. There seems little dispute about that. > > But then we add attributes, and it starts to get a little odd: > > class Point { > public function __construct( > <<Positive>> > protected int $x, > > <<Positive>> > protected int $y, > ) {} > } > > And has the possible confusion between parameter attributes and property > attributes. > > But... there's plenty of other things that may get added to properties. > We've discussed the "readonly" property and the more robust assymetric > visibility. What would that look like? > > class Point { > public function __construct( > <<Positive>> > int $x { > public get, > private set, > }, > > <<Positive>> > int $y { > public get, > private set, > }, > ) {} > } > > And that's now an awful lot of metadata to shove into, technically, a > function signature. And if accessor methods ever happen: > > class Point { > public function __construct( > <<Positive>> > int $x { > public get () { > return $this->x; > }, > private set($new) { > if ($new < 0) { > throw new Exception(); > } > $this->x = $new; > }, > }, > > <<Positive>> > int $y { > public get () { > return $this->y; > }, > private set($new) { > if ($new < 0) { > throw new Exception(); > } > $this->y = $new; > }, > }, > ) {} > } > > And now we have methods defined inside a function signature, and that's > just bizzarro. It also means at least 4 indent levels before you even get > to the body of the methods. > > And there's probably other things that could get attached to properties at > some point I'm not thinking of that someone will suggest. > > So I have to wonder, are we painting ourselves into a hole? Most of the > uses I can think of for extra-metadata-properties are... overlapping with > the case where you want to have constructor promotion. That means "well if > you need to do fancy stuff just don't use promotion" becomes a > non-sustainable answer, because the cases where you want both will increase > rather than be an edge case. > > Is there a way we can build in an escape hatch for ourselves to avoid > these issues, both attributes and for future extension? > > First idea off my head, which may or may not be good: > > Allow an alternate keyword to fill in the body of the constructor, but NOT > fill in the property. You still define the property yourself and put all > the extra metadata there. To wit: > > class Point { > <<Positive>> > int $x { > public get () { > return $this->x; > }, > private set($new) { > if ($new < 0) { > throw new Exception(); > } > $this->x = $new; > }, > }, > > <<Positive>> > int $y { > public get () { > return $this->y; > }, > private set($new) { > if ($new < 0) { > throw new Exception(); > } > $this->y = $new; > }, > }, > > public function __construct(promote int $x, promote int $y) {} > } > > That eliminates 2 of the 4 places you'd have to repeat the name instead of > 3, but means the constructor isn't burdened with having to hold a > potentially non-trivial amount of metadata. It also resolves the attribute > question as splitting it up like this would let you do "one or the other" > (property vs parameter). > > That may not be the best way, but I'm slightly uneasy with a path that > could lead to 30 lines of property metadata squeezed into a constructor > signature definition. :-) > Hey Larry, At least for me, there's a pretty clear line on what is acceptable to use with constructor promotion and what isn't: That line has been crossed when your property declarations started to include their own {} blocks. Constructor promotion is a short-hand notation for the cases where the constructor boilerplate is much larger than the actual functionality. If you have 100 lines of property declarations due to heavy accessor use, then I think the value proposition of constructor promotion converges to zero, because the constructor boilerplate is no longer a dominating factor. Regarding your last example, something similar to this has already been proposed (and declined) in a slightly different form in https://wiki.php.net/rfc/automatic_property_initialization: class Point { public int $x { ... }; public int $y { ... }; public function __construct(int $this->x, int $this->y) {} } Regards, Nikita