Le lun. 24 févr. 2020 à 21:35, Larry Garfield <la...@garfieldtech.com> a écrit :
> On Mon, Feb 24, 2020, at 7:55 AM, Rowan Tommins wrote: > > On Fri, 21 Feb 2020 at 23:18, Larry Garfield <la...@garfieldtech.com> > wrote: > > > > > The with*() method style requires cloning the object. What happens to > the > > > locked status of a set property if the object is cloned? Are they then > > > settable again, or do they come pre-locked? > > > > > > Neither of those seem good, now that I think about it. If they come > > > pre-locked, then you really can't clone, change one property, and > return > > > the new one (as is the standard practice now in that case). If they > don't > > > come pre-locked, then the newly created object can have everything on > it > > > changed, once, which creates a loophole. I'm not sure what the right > > > answer is here. > > > > > > > > > As with typed properties, I wonder if there's a way we can introduce a > new > > initialisation sequence for objects, so that there's a specific point > where > > the object is considered "fully constructed" after new or clone. > > > > A couple of brainstormed ideas, with plenty of downsides I'm sure: > > > > An explicit finalise() function or keyword > > > > public function withFoo($foo) { > > $inst = clone $this; > > // all readonly properties are initially "unlocked" > > $inst->foo = $foo; > > // now lock them, perhaps also checking that no typed properties are > > left uninitialised > > finalise($inst); // or finalise $inst; > > return $inst; > > } > > > > A special code block: > > > > public function withFoo($foo) { > > $inst = clone $this { > > // all properties are "unlocked" within this special block > > $inst->foo = $foo; > > }; > > // from here onwards, readonly properties can't be written to > > return $inst; > > } > > > > Perhaps could also be used with constructors: > > > > public function createFromOtherThing(OtherThing $other) { > > $inst = new static('some parameter') { > > // readonly properties can be written in the constructor, or > > within this block > > $inst->foo = $other->getFoo(); > > }; > > // object is "finalised" when the block ends > > return $inst; > > } > > If the way to resolve this question is a special "unlocked" mode, I would > definitely favor the explicit code block. That way it's self-closing and > you can't forget to do so. (Murphy's Law: If you rely on developers > remembering to do X to keep code safe, they will promptly forget to do X.) > > Also, FTR, any approach that forces developers to write a 9 parameter > constructor over and over is one I can never get behind, doubly so for > something that is currently only a single line. This isn't a case of "pain > tells you what not to do"; a value object having a lot of internal > properties is a completely valid use case, and "but composition" is not an > answer. I totally agree with this: there must be a way to work around the keyword - either via reflection or another means. Unlike `private`, `final` is an imperative keyword that cannot be bypassed, while there are very legit use cases for still extending final classes (proxies). For sure, the same will be legitimately needed with `readonly`. Via Reflection, it could be a new method `->setWritable(true)` (next to `->setAccessible(true)`). Another way, which is my current preference, would be to have visibility be taken into consideration when using the keyword: - `public readonly $foo` => cannot be set from the outside of the class - but can be from protected+private scopes - `protected readonly $foo` => can be set only from private scope - `private readonly $foo` => either unsupported or have the `writeonce` behavior defined in the RFC. This would make the keyword compatible with untyped properties and with cloning. It would provide the guarantee we need from an author pov: outside scopes cannot mess up with the state of properties. It would not provide authors a safeguard against their own mistakes - but I think that's a really secondary goal and one that is fine letting go vs the mentioned benefits. Nicolas