[RFC] [DISCUSSION] Immutable/final/readonly properties

php.internals

Máté Kocsis

6 years ago
Hi Internals, I'd like to move my RFC forward to the discussion phase: https://wiki.php.net/rfc/write_once_properties In short, I propose to add support for a new property modifier that would allow properties to be initialized, but not modified afterwards. Cheers, Máté Kocsis

Marco Pivetta

6 years ago
Hey Mark, Asking here as well, so that others are aware: is `unset($this->aPropertyThatWasNotInitializedBefore)` still working with this patch? Also, are the semantics around `__get`, `__set` and `__isset` the same as with typed properties? In the RFC, you have `unset($foo->c)` as disallowed: the property isn't declared nor initialized though. I'd expect this to fail only after first initialization? Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/ On Wed, Feb 19, 2020 at 6:06 PM Máté Kocsis <kocsismate90@gmail.com> wrote:

Larry Garfield

6 years ago
On Wed, Feb 19, 2020, at 11:05 AM, Máté Kocsis wrote:
> Hi Internals, > > I'd like to move my RFC forward to the discussion phase: > https://wiki.php.net/rfc/write_once_properties > > In short, I propose to add support for a new property modifier that would > allow properties to be initialized, but not modified afterwards. > > Cheers, > Máté Kocsis
As envisoned, does this allow for a property to be set to a dynamic value? My concern is that while a public locked/writeonce property is great for access, it doesn't do anything to enable lazy setting on first access. In fact the only way to do that would be to make it private and wrap access in a method, which would look exactly like that does now but with an extra keyword that doesn't actually offer much. You could set the value in advance in the constructor, but then it's not lazy, just locked. Is there a way it could support lazy-on-first-use then locked? --Larry Garfield

Marco Pivetta

6 years ago
On Wed, Feb 19, 2020, 19:51 Larry Garfield <larry@garfieldtech.com> wrote:
> On Wed, Feb 19, 2020, at 11:05 AM, Máté Kocsis wrote: > > Hi Internals, > > > > I'd like to move my RFC forward to the discussion phase: > > https://wiki.php.net/rfc/write_once_properties > > > > In short, I propose to add support for a new property modifier that would > > allow properties to be initialized, but not modified afterwards. > > > > Cheers, > > Máté Kocsis > > As envisoned, does this allow for a property to be set to a dynamic > value? My concern is that while a public locked/writeonce property is > great for access, it doesn't do anything to enable lazy setting on first > access. In fact the only way to do that would be to make it private and > wrap access in a method, which would look exactly like that does now but > with an extra keyword that doesn't actually offer much. > > You could set the value in advance in the constructor, but then it's not > lazy, just locked. > > Is there a way it could support lazy-on-first-use then locked?
Máté did run a few teats: operating on a lazy value (before initialisation) seems to work as expected 👍

Larry Garfield

6 years ago
On Wed, Feb 19, 2020, at 1:36 PM, Marco Pivetta wrote:
> On Wed, Feb 19, 2020, 19:51 Larry Garfield <larry@garfieldtech.com> wrote: > > > On Wed, Feb 19, 2020, at 11:05 AM, Máté Kocsis wrote: > > > Hi Internals, > > > > > > I'd like to move my RFC forward to the discussion phase: > > > https://wiki.php.net/rfc/write_once_properties > > > > > > In short, I propose to add support for a new property modifier that would > > > allow properties to be initialized, but not modified afterwards. > > > > > > Cheers, > > > Máté Kocsis > > > > As envisoned, does this allow for a property to be set to a dynamic > > value? My concern is that while a public locked/writeonce property is > > great for access, it doesn't do anything to enable lazy setting on first > > access. In fact the only way to do that would be to make it private and > > wrap access in a method, which would look exactly like that does now but > > with an extra keyword that doesn't actually offer much. > > > > You could set the value in advance in the constructor, but then it's not > > lazy, just locked. > > > > Is there a way it could support lazy-on-first-use then locked? > > > Máté did run a few teats: operating on a lazy value (before initialisation) > seems to work as expected 👍
Erm. I don't think I was clear in my intent. I would expect that operating on one of these properties before it's initialized will throw an error: class Foo { readonly public string $bar; } $f = new Foo(); print $f->bar; // This should fail. My question is whether this will work: class Foo { readonly public string $bar = $this->value(); private function value(): string { return "hello"; } } $f = new Foo(); print $f->bar; // I would want this print "hello". Does that work currently or no? If so, this is pretty sweet. If not, it seems to be of limited use. --Larry Garfield

Máté Kocsis

6 years ago
> > I would expect that operating on one of these properties before it's > initialized will throw an error: >
Actually, the RFC only says that the "immutability" of properties is guaranteed after initialization. We could of course change this premise, but that would make some important use-cases (like what Marco's ProxyManager has) impossible to do. Furthermore, unsetting properties could be completely disabled by another RFC, given there is a migration path for the legitimiate use-cases. As far as I remember, the "Locked Classes" ( https://wiki.php.net/rfc/locked-classes) and lately the "Rigid Properties" (https://github.com/php/php-src/pull/5170) RFCs try to have their shots at it.
> Does that work currently or no? If so, this is pretty sweet. If not, it > seems to be of limited use. >
No, it doesn't work, and it seems to be an unrelated feature for me. As far as I understand your example, it's the topic of the "Constant expressions" RFC. I believe the behaviour proposed by my RFC would be still useful in many cases where one wants to be sure that no unexpected modifications can happen with a property. My use-cases would mainly include objects storing different kind of data: events, value objects, data transfer objects. Máté Kocsis

Matthew Brown

6 years ago
Someone recently requested something similar for a PHP static analysis tool I wrote (https://psalm.dev/r/f75997a263), though that version only allows lazy initialisation inside the class in which a given property is declared. Personally I don't like either approach – I think per-property getters and setters would be a more appropriate venue for this functionality, something like: property int $s { public get; private set; } This pattern could also be extended to support per-property getter and setter methods. On Wed, 19 Feb 2020 at 12:06, Máté Kocsis <kocsismate90@gmail.com> wrote:

Nikita Popov

6 years ago
On Thu, Feb 20, 2020 at 1:27 AM Matthew Brown <matthewmatthew@gmail.com> wrote:
> Someone recently requested something similar for a PHP static analysis tool > I wrote (https://psalm.dev/r/f75997a263), though that version only allows > lazy initialisation inside the class in which a given property is declared. > > Personally I don't like either approach – I think per-property getters and > setters would be a more appropriate venue for this functionality, something > like: > > property int $s { > public get; > private set; > } > > This pattern could also be extended to support per-property getter and > setter methods. >
While I certainly like the idea of per-property getters/setters, I think that both of these have their place. This RFC proposes readonly properties, that can be initialized once, and then never modified again, even within the same class. Getters/setters only provide the possibility of having asymmetric visibility: They prevent modifying the property from outside the class, but it can still be modified inside the class. In that, readonly properties offer a stronger guarantee. Of course, that does leave the question of how often you need one or the other. Maybe just the asymmetric visibility is sufficient for most practical purposes, in which case it may not be worthwhile to introduce readonly properties as a separate feature. Two comments on the specifics of the RFC: The RFC allows specifying a default value for readonly properties. However, a property for which a default value has been specified will always have that value, as it cannot be overwritten in the constructor. If you write "public readonly int $foo = 42", then $object->foo is *always* going to be 42. I'm not sure what that would ever be useful for, and it seems like something that is bound to be confusing. Maybe it would make more sense to forbid readonly properties with default values? (That way, the rule that readonly properties have to be typed falls out naturally as well -- untyped properties always have a default.) Regarding the keyword choice, I think you can drop "sealed" from the list, as it is an established term that affects inheritance, not mutability. Of the choices you present, "immutable", "readonly" and "writeonce" seem like the most viable candidates. "writeonce", while the one that is most technically accurate, is also *unnecessarily* technically accurate and not intuitive. From the perspective of an API consumer, I think that "readonly" is the most accurate description of how they are supposed to interact with the property. The API contract you want to expose is that they can only read from the property, not write to it. Calling it "writeonce" would be quite confusing in that context, because the API consumer is never expected to write to the property. In the majority of cases you will be providing fully initialized objects, in which case they are indeed readonly for the consumer -- the details of the write-once property are only relevant in special cases like ReflectionClass::newObjectWithoutConstructor() for serialization libraries, or lazy initialization like in Marco's ProxyManager. Regards, Nikita

Larry Garfield

6 years ago
On Thu, Feb 20, 2020, at 3:06 AM, Nikita Popov wrote:
> On Thu, Feb 20, 2020 at 1:27 AM Matthew Brown <matthewmatthew@gmail.com> > wrote: > > > Someone recently requested something similar for a PHP static analysis tool > > I wrote (https://psalm.dev/r/f75997a263), though that version only allows > > lazy initialisation inside the class in which a given property is declared. > > > > Personally I don't like either approach – I think per-property getters and > > setters would be a more appropriate venue for this functionality, something > > like: > > > > property int $s { > > public get; > > private set; > > } > > > > This pattern could also be extended to support per-property getter and > > setter methods. > > > > While I certainly like the idea of per-property getters/setters, I think > that both of these have their place. This RFC proposes readonly properties, > that can be initialized once, and then never modified again, even within > the same class. Getters/setters only provide the possibility of having > asymmetric visibility: They prevent modifying the property from outside the > class, but it can still be modified inside the class. In that, readonly > properties offer a stronger guarantee. > > Of course, that does leave the question of how often you need one or the > other. Maybe just the asymmetric visibility is sufficient for most > practical purposes, in which case it may not be worthwhile to introduce > readonly properties as a separate feature. > > Two comments on the specifics of the RFC: > > The RFC allows specifying a default value for readonly properties. However, > a property for which a default value has been specified will always have > that value, as it cannot be overwritten in the constructor. If you write > "public readonly int $foo = 42", then $object->foo is *always* going to be > 42. I'm not sure what that would ever be useful for, and it seems like > something that is bound to be confusing. Maybe it would make more sense to > forbid readonly properties with default values? (That way, the rule that > readonly properties have to be typed falls out naturally as well -- untyped > properties always have a default.) > > Regarding the keyword choice, I think you can drop "sealed" from the list, > as it is an established term that affects inheritance, not mutability. Of > the choices you present, "immutable", "readonly" and "writeonce" seem like > the most viable candidates. "writeonce", while the one that is most > technically accurate, is also *unnecessarily* technically accurate and not > intuitive. From the perspective of an API consumer, I think that "readonly" > is the most accurate description of how they are supposed to interact with > the property. The API contract you want to expose is that they can only > read from the property, not write to it. Calling it "writeonce" would be > quite confusing in that context, because the API consumer is never expected > to write to the property. In the majority of cases you will be providing > fully initialized objects, in which case they are indeed readonly for the > consumer -- the details of the write-once property are only relevant in > special cases like ReflectionClass::newObjectWithoutConstructor() for > serialization libraries, or lazy initialization like in Marco's > ProxyManager. > > Regards, > Nikita
> From Máté Kocsis <kocsismate90@gmail.com> wrote:
> No, it doesn't work, and it seems to be an unrelated feature for me. As far > as I understand your > example, it's the topic of the "Constant expressions" RFC. > > I believe the behaviour proposed by my RFC would be still useful in many > cases where one wants > to be sure that no unexpected modifications can happen with a property. My > use-cases > would mainly include objects storing different kind of data: events, value > objects, data transfer objects.
Yeah, I'm definitely thinking in relation to the earlier discussion, since I think they're all inter-related. (This, property accessors, and constant expressions.) As Nikita notes above, a read-only property with a default value is... basically a constant already. So that's not really useful. For defined-later readonly properties, I'm not sure how the earlier point about reading an unintialized property isn't valid. Currently: class Foo { public string $bar; } $f = new Foo(); print $f->bar; // this throws a TypeError. I would expect the exact same behavior if $bar were marked readonly/locked/whatever. Are you saying that's not the case? I do think it's fair to bring in the property accessor discussion here, as property accessors would allow for the same net functionality as this property as a special case, albeit with likely more syntax. AFAIR the issue with them before wasn't that people opposed the idea, just the performance impact. If we could address the performance impact, that would give us much more functionality-for-the-buck, including an equivalent of read-only properties including potentially lazy initialization. Or derive-on-demand behavior would also be a big increase in functionality. It's not that I don't see a value to this RFC; I actually have a few places in my own code where I could use it. It's that I see it as being of fairly narrow use, so I'm trying to figure out how to increase it so that the net-win is worth it. --Larry Garfield

Máté Kocsis

6 years ago
> > Yeah, I'm definitely thinking in relation to the earlier discussion, since > I think they're all inter-related. (This, property accessors, and constant > expressions.) >
The biggest question is whether it's worth to support both readonly properties and property accessors. My answer is clear yes, because there are many-many ways to mess with private or protected properties without and public setters from the outside - in which case property accessors couldn't help much. I collected some examples I know of: https://3v4l.org/Ta4PM Please note that the first two examples also apply to private properties, while the last one only applies to protected ones.
> As Nikita notes above, a read-only property with a default value is... > basically a constant already. So that's not really useful. >
I agree that they are not very useful, however I wouldn't restrict their usage. Mainly because there are probably some legitimate use-cases, but I also think it would be advantageous to be consistent with the other languages in this case.
> For defined-later readonly properties, I'm not sure how the earlier point > about reading an unintialized property isn't valid. Currently: > > class Foo { > public string $bar; > } > > $f = new Foo(); > print $f->bar; // this throws a TypeError. > > I would expect the exact same behavior if $bar were marked > readonly/locked/whatever. Are you saying that's not the case? >
Sorry if I didn't exactly get the question/example, but what I can tell you is that currently an Error exception is thrown with the "Typed property Foo::$bar must not be accessed before initialization" message, and it would be the case with my patch as well since it doesn't affect the reading side. The situation is the same when it comes to unsetting uninitialized typed properties. Currently, these properties can be unset with no problem (and the __get(), __set() etc. magic methods are then invoked when accessing them), and the same would happen with my patch.
> If we could address the performance impact, that would give us much more > functionality-for-the-buck, including an equivalent of read-only properties > including potentially lazy initialization. Or derive-on-demand behavior > would also be a big increase in functionality. > > It's not that I don't see a value to this RFC; I actually have a few > places in my own code where I could use it. It's that I see it as being of > fairly narrow use, so I'm trying to figure out how to increase it so that > the net-win is worth it. >
The reason why I brought up this RFC is that I'd really like to add first-class support for immutable objects, and it seemed to be a good idea to first go for readonly properties. This way, the scope of an immutable object RFC gets smaller, while it's possible to only have readonly properties alone. Regards, Máté

Larry Garfield

6 years ago
On Fri, Feb 21, 2020, at 4:29 AM, Máté Kocsis wrote:
> > > > Yeah, I'm definitely thinking in relation to the earlier discussion, since > > I think they're all inter-related. (This, property accessors, and constant > > expressions.) > > > > The biggest question is whether it's worth to support both readonly > properties and property accessors. My answer is clear yes, because there > are many-many > ways to mess with private or protected properties without and public > setters from the outside - in which case property accessors couldn't help > much. I collected some examples I know of: > https://3v4l.org/Ta4PM
I didn't even know you could do some of those. That's horrifying. :-)
> > As Nikita notes above, a read-only property with a default value is... > > basically a constant already. So that's not really useful. > > I agree that they are not very useful, however I wouldn't restrict their > usage. Mainly because there are probably some legitimate use-cases, but I > also think it would > be advantageous to be consistent with the other languages in this case.
If they could do something that class constants can't, that would make them useful. If not, then I feel like it would just be introducing new syntax for the same thing, without much benefit. (I'm thinking of, eg, could you set them by default to a new Foo() object, which you could then modify the Foo but not change it for another object, thus moving that initialization out of the constructor? That sort of thing.)
> > If we could address the performance impact, that would give us much more > > functionality-for-the-buck, including an equivalent of read-only properties > > including potentially lazy initialization. Or derive-on-demand behavior > > would also be a big increase in functionality. > > > > It's not that I don't see a value to this RFC; I actually have a few > > places in my own code where I could use it. It's that I see it as being of > > fairly narrow use, so I'm trying to figure out how to increase it so that > > the net-win is worth it. > > > > The reason why I brought up this RFC is that I'd really like to add > first-class support for immutable objects, and it seemed to be a good idea > to first go for readonly properties. > This way, the scope of an immutable object RFC gets smaller, while it's > possible to only have readonly properties alone. > > Regards, > Máté
I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? 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. My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) --Larry Garfield

Andreas Hennings

6 years ago
When writing immutable classes, I want to be able to set properties in static factories and in wither methods. Once the new instance is sent to the outside world, its properties can be locked to prevent further modification. This sounds to me like we need different modes. Either the object itself would have different states over time, or the object stays the same and instead some methods have mutation permission on newly created objects. This could be seen as a runtime state problem or as a compile time code verification problem. On Sat, 22 Feb 2020, 00:18 Larry Garfield, <larry@garfieldtech.com> wrote:

Mike Schinkel

6 years ago
> On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: > I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? > > The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? > > 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. > > My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure.
> > How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.)
I have struggled to follow this RFC thread fully, so if I am getting something out of context, please note that and I apologize in advance. However, it would see that rules for `write once` properties to support lazy loading would be rather simple: 1. Write-once properties can only be updated once. 2. Write-once properties can only be updated within the class where they are declared. 3. If you want to update a property from outside the class, create a set_<property>() method to allow it to happen. 4. If you do not want it to be set externally, do not implement a set_<property>() method. 5. If you want it to be implemented externally sometimes but not others, implement guard classes inside the set_<property>() method. I think that addresses all scenarios, no? -Mike

Larry Garfield

6 years ago
On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote:
> > On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? > > > > The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? > > > > 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. > > > > My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. > > > > > How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) > > > I have struggled to follow this RFC thread fully, so if I am getting > something out of context, please note that and I apologize in advance. > > However, it would see that rules for `write once` properties to support > lazy loading would be rather simple: > > 1. Write-once properties can only be updated once. > 2. Write-once properties can only be updated within the class where > they are declared.
This is the common use case I think many envision, but nothing in the proposal requires that. A public write-once property (as currently written) would be world-readable, and world-writeable, once. Separate visibility for internal and external access is a separate matter. (Also potentially useful, but not part of the write-once proposal at the moment.)
> 3. If you want to update a property from outside the class, create a > set_<property>() method to allow it to happen. > 4. If you do not want it to be set externally, do not implement a > set_<property>() method. > 5. If you want it to be implemented externally sometimes but not > others, implement guard classes inside the set_<property>() method. > > I think that addresses all scenarios, no? > > -Mike
It does not. 1) Race condition if I assume that a public write-once property is a materialized value, but access it before it gets materialized. 2) Race condition if internal non-constructor code wants to set the value, but some external routine has set it first. 3) Cloning creates an interesting and complicated case of both of the above. Does a cloned object start with its write-once bits reset or no? There's problems both ways. Making a write-once property implicitly write-only-from-inside-the-class would help address point 2, but not points 1 or 3. Adding separate get/set visibility modifiers is another interesting idea, but is separate and should be evaluated on its own merits. It is not at this time part of this proposal. --Larry Garfield

Marco Pivetta

6 years ago
Hey Larry, On Sun, Feb 23, 2020, 18:06 Larry Garfield <larry@garfieldtech.com> wrote:
> > It does not. > > 1) Race condition if I assume that a public write-once property is a > materialized value, but access it before it gets materialized. >
This is alrey true for any public typed properties without default values, and why static analysis tools pick up missing initialisation since ages.
> 2) Race condition if internal non-constructor code wants to set the value, > but some external routine has set it first. >
Internal ctors are part of the category above: tools like psalm pick this up as well.
> 3) Cloning creates an interesting and complicated case of both of the > above. Does a cloned object start with its write-once bits reset or no? > There's problems both ways. >
Not a problem: we seem to be making a problem out of the pattern used in current PSR-7 implementations, which is following: final class Foo { public function withBar($bar):self { $instance = clone $this; $instance->foo = $foo; return $instance; } } The solution is trivial: don't use cloning: final class Foo { public function withBar($bar):self { $instance = new self(); $instance->foo = $foo; // more assignments here - unavoidable return $instance; } }

Rowan Collins

6 years ago
On 24 February 2020 03:26:19 GMT+00:00, Marco Pivetta <ocramius@gmail.com> wrote:
>The solution is trivial: don't use cloning: > >final class Foo >{ > public function withBar($bar):self { > $instance = new self(); > $instance->foo = $foo; > // more assignments here - unavoidable > return $instance; > } >}
This works fine on paper, but is completely impractical as the class grows. Consider a class with 10 such properties, each with their own with* method: switching from clone to explicit assignments means adding 90 lines of code, all of it copy-and-pasted boilerplate that's hard to spot mistakes in. It's also impossible to use with inheritance, or to compose with traits (as Diactoros does, for instance), because every with* method needs to know the full details of how to create a partial clone. Regards,
-- Rowan Tommins [IMSoP]

Mike Schinkel

6 years ago
> On Feb 23, 2020, at 12:06 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote: >>> On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: >>> I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? >>> >>> The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? >>> >>> 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. >>> >>> My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. >> >>> >>> How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) >> >> >> I have struggled to follow this RFC thread fully, so if I am getting >> something out of context, please note that and I apologize in advance. >> >> However, it would see that rules for `write once` properties to support >> lazy loading would be rather simple: >> >> 1. Write-once properties can only be updated once. >> 2. Write-once properties can only be updated within the class where >> they are declared. > > This is the common use case I think many envision, but nothing in the proposal requires that. A public write-once property (as currently written) would be world-readable, and world-writeable, once. > Separate visibility for internal and external access is a separate matter. (Also potentially useful, but not part of the write-once proposal at the moment.)
This just hit me, so I think I will mention it. The culture on the list seems not to be one of collaboration on RFC to find solutions to improve PHP but instead waiting for someone to stick their neck out with an RFC and then see who can shoot the most holes through it. I did not actually expect that. I would have hoped for a collaborate culture of the nature I am used to in local startup-oriented meetups where most everyone is trying to help each other rather than just pointing out that the work someone else has done is deficient and not worthy of moving forward. If that is the desired culture that most want to have on this list, then I will accept it even though I will lament how much better it could be otherwise. If I misunderstand, please help me understand how wording like "but nothing in the proposal requires that" and "not part of the write-once proposal at the moment" rather than "the proposal does not require that but lets's explore how adding that requirement can make the proposal better" should not leave the impression I just mentioned above? Or maybe the problem is the mailing list is just not a mechanism for collaborative work and we need a new mechanism. Like Github, comments, and pull requests? Moving on...
>> 3. If you want to update a property from outside the class, create a >> set_<property>() method to allow it to happen. >> 4. If you do not want it to be set externally, do not implement a >> set_<property>() method. >> 5. If you want it to be implemented externally sometimes but not >> others, implement guard classes inside the set_<property>() method. >> >> I think that addresses all scenarios, no? >> >> -Mike > > It does not. > > 1) Race condition if I assume that a public write-once property is a materialized value, but access it before it gets materialized.
Can you please explain what "materialized value" means in this context? Are you speaking in Scala again? If you mean that a write-once property just has not yet been assigned, I am not sure how that differs from just accessing a property that has not yet been assigned today?
> 2) Race condition if internal non-constructor code wants to set the value, but some external routine has set it first.
As I said, don't let the external routine set it directly. Which you did note. (I am interested in finding solutions in the same solution space as selected proposals and not just limiting myself to the exact specifics of the proposal because IMO limiting ourselves in that way has all the bad aspects of bureaucracy and none of the good aspects.)
> 3) Cloning creates an interesting and complicated case of both of the above. Does a cloned object start with its write-once bits reset or no? There's problems both ways.
If there are problems, let us find solutions. 1. Only allow internal methods to change write-once properties. 2. Options: 2a. Extend clone statement to have `clone rewrite $foo` that would allow rewriting the write-once properties. 2b. Allow rewriting once of write-once object properties until some operation is performed to seal or lock the object (operation being a method call, a function call with the object as param, or a statement with the object as argument.) 2c. Don't allow rewriting of cloned objects. See if it is really a problem. Fix it in a future RFC if so. 2d. Something else I am someone we can think of. -Mike

Terje Slettebø

6 years ago
Mike Schinkel wrote:
>> >>>> On Feb 21, 2020, at 6:17 PM, Larry Garfield >>>> <larry@garfieldtech.com> wrote: >> >> Separate visibility for internal and external access is a separate >> matter. (Also potentially useful, but not part of the write-once >> proposal at the moment.) >> > This just hit me, so I think I will mention it. > > The culture on the list seems not to be one of collaboration on RFC to > find solutions to improve PHP but instead waiting for someone to stick > their neck out with an RFC and then see who can shoot the most holes > through it.
Hi Mike. I'm a newcomer to this list, but many years ago, I was more active on the Boost C++ libraries list, where they have something called "formal review" and it's not for the faint of heart. In order for a library to be accepted as a Boost library, it has to go through a rather rigorous review, where everyone and anyone are allowed to poke at it and try to "shoot holes through it". This is not in any way done to discourage library writers to come up with proposals, rather the opposite: By subjecting a proposal to such scrutiny, where - like you said - people may also propose changes to deal with those issues, the proposals become that much stronger. This is similar to how science works: When someone proposes a theory, they way it's tested to check if it's any good is to essentially do "destructive testing": Try to break it, usually done first by the originator of the theory, and later by the scientific community. Take the theory of general relativity: This is one of the most well-tested theories we have, and it has passed every test thrown at it with flying colours. This is the light in which I see the reviews/discussions on this list as well: By trying to "test" proposals even in extreme ways, we learn if they are any good or not, or if they have some fundamental weaknesses that we need to address. Better to find that out now, than after it has been accepted into the language. I also think what is important is that these discussions happen in a professional and respectful way, and if you do that, then I think there's no limit to how much you may "tear a proposal apart", again, which ultimately leads to a stronger proposal, or a revision or retraction, if we find there are some serious issues that warrant a reconsideration. Now to the subject matter: I'm in favour of simple and straight rules, and I'm wondering if we might try a little too much if we are to cater for both write-once properties and lazy loading. To take how you might do it in C++, you'd make a regular class, and then make a const instance of it: const User user(...); When the constructor runs, it sets all object properties, and upon completion, they are immutable. We don't yet have the possibility to declare const objects (although this proposal mentions it as a future RFC: https://wiki.php.net/rfc/declare_vars), but maybe we could consider going this route? Using PHP syntax, we may write: const $user = new User(...); Following the construction, the object is considered const/read-only, and you can't change its object properties. There's no need to declare each property as "readonly". What about lazy loading? Well, C++ has a solution for this, as well: If you need to be able to change properties for a const object, you can mark them "mutable". I know, this doesn't address the issue of actually preventing them to be changed after having been set once, but I thought I'd at least inspire people to think about how these things are done in other languages. Regards, Terje

Larry Garfield

6 years ago
On Mon, Feb 24, 2020, at 3:25 PM, Mike Schinkel wrote:
> > On Feb 23, 2020, at 12:06 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > > > On Sun, Feb 23, 2020, at 2:39 AM, Mike Schinkel wrote: > >>> On Feb 21, 2020, at 6:17 PM, Larry Garfield <larry@garfieldtech.com> wrote: > >>> I'm totally on board for better value object support, so that's a good motive for me. The question I have is whether this is really a good stepping stone in that direction or if it would lead down a wrong path and lock us into too much TIMTOWTDI (for the Perl fans in the room). So let's think that through down that path. How would write-once properties lead into properly immutable value objects? Or do they give us that themselves? > >>> > >>> The biggest challenge for immutable objects, IMO, is evolving them. Eg, $result->withContentType(...) to use the PSR-7 example. Would we expect people to do it with a method like that, or would there be some other mechanism? If the properties are public, would we offer a more syntactic way to modify them directly? > >>> > >>> 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. > >>> > >>> My other concern is a public property (the most likely use case) would have to be set in the constructor. If it's not, then callers cannot rely on it having been set yet if it's set lazily. And if code inside the class tries to set it lazily, it may already have been set by some external code (rightly or wrongly) and cause a failure. > >> > >>> > >>> How do we address that? There's absolutely use cases where setting everything in the constructor ahead of time is what you'd do anyway, but there are plenty where you wouldn't want to, either, which creates a race condition for who sets it first, or tries to access it before it gets set, etc. (This is where my repeated questions about lazy initialization come from.) > >> > >> > >> I have struggled to follow this RFC thread fully, so if I am getting > >> something out of context, please note that and I apologize in advance. > >> > >> However, it would see that rules for `write once` properties to support > >> lazy loading would be rather simple: > >> > >> 1. Write-once properties can only be updated once. > >> 2. Write-once properties can only be updated within the class where > >> they are declared. > > > > This is the common use case I think many envision, but nothing in the proposal requires that. A public write-once property (as currently written) would be world-readable, and world-writeable, once. > > Separate visibility for internal and external access is a separate matter. (Also potentially useful, but not part of the write-once proposal at the moment.) > > This just hit me, so I think I will mention it. > > The culture on the list seems not to be one of collaboration on RFC to > find solutions to improve PHP but instead waiting for someone to stick > their neck out with an RFC and then see who can shoot the most holes > through it. > > I did not actually expect that. I would have hoped for a collaborate > culture of the nature I am used to in local startup-oriented meetups > where most everyone is trying to help each other rather than just > pointing out that the work someone else has done is deficient and not > worthy of moving forward.
Mike, Terje already responded here and he is spot on: "Destructive testing" is a very good metaphor to use, and is what is happening in this thread. Evolving a language like PHP, with millions of users and billions of lines of code, is not, even a little, like a startup-oriented meetup. In a meetup, "yes and" is a perfectly good model, and you can encourage everyone to make their own thing, throw it out into the market, and see what happens. Multiple incompatible and competing startups can succeed and the ecosystem is better for it. But there is only one PHP language, and throwing everything and the kitchen sink into it just to make people feel good and inspired is a superb way to ruin the language. *Every* new feature or syntax addition creates cognitive overhead for *millions* of people. If done in a way that has lots of holes or flaws, it can create billion-dollar bugs that take a decade to unravel, if ever. The cost of "getting it wrong" is high. The cost/benefit analysis has to be very strong to justify adding syntactic weight to every PHP user in the world. That does not mean that we should oppose all change; hell, PHP is perhaps the fastest evolving production language in the world right now. There's a long list of recent features I've supported and cheered for, and a long list I'd still like to see. The same is true of almost everyone on this list. But in order to keep PHP a good, approachable, flexible language and keep it consistent (well, at least no more inconsistent than it already is), every one of those features needs to "run the gauntlet" of destructive testing, poking holes in it, and finding all the gotchas. That's exactly the point: We *do* want to find all the little gotchas in a proposal, now, before something gets into the language and the PHP-coding world finds them instead. Constructive nitpicking and destructive testing, in this context, are a sign of respect. If folks on the list didn't respect this proposal, it would have been met with "this is stupid", "we went over this, it's dumb", "why are you wasting our time?", or other such responses. (Certainly there have been proposals met with such response in the past.) Or, simply ignored outright. Instead, it's being met with "Hm, someone is going to try and throw this against a wall harder than they have any right to; so let's do it now and see what happens so we can figure out how to make it not shatter into a thousand pieces." That's a good thing. Numerous people are donating a not-small amount of their time to stress-test the proposal. Some of that work has already improved the proposal, eg, the general agreement that it was best to just ignore untyped properties and references entirely. That's a good thing. Máté has said that his end goal is immutable objects, and this is a step in that direction. I fully support that goal. Is this actually the right first step in that direction, or does it go down a wrong path? I don't know yet; that's what all of the nitpicking and hypotheticals and "running it over with a car to see what happens" is about: Figuring out if it is the right way to get to that goal. I'm glad we're having this conversation. Maybe a readonly flag only makes sense if we first/also add separate visibility for getters and setters? I don't know, but it's worth asking. Does it break cloning? Seems like it, but maybe there's a way around it. Would some totally different approach get to a better end-state with fewer edge cases? Could be. Those are realizations that only happen when you take an RFC and "shoot the most holes through it", because then you find the places where it's not bulletproof. That's the process working as designed.
> > 1) Race condition if I assume that a public write-once property is a materialized value, but access it before it gets materialized. > > Can you please explain what "materialized value" means in this context? > Are you speaking in Scala again?
I don't speak Scala. If anything it's an SQL reference. A "materialized table" is basically a saved query result, which you can then reuse over and over again, that self-updates when its underlying source changes. Think $user->fullName, where fullName is a public read-only property derived by concatenating $firstName and $lastName. If that is set in the User constructor, great, no problem. If it's not, because it's not always needed so you want to only do that work if it's going to be used, then the caller doesn't know if it's already been set or if it's still undefined. Or, if public write isn't blanket-disallowed (which could be done but I suspect would lead to other limitations), whatever internal code would set the value doesn't know if it's already been set by an over-eager external routine (possibly incorrectly).
> If you mean that a write-once property just has not yet been assigned, > I am not sure how that differs from just accessing a property that has > not yet been assigned today?
It doesn't, which is one of the reasons public properties are broadly frowned upon today. However, most of the use cases of a readonly flag I can see apply to publicly-readable properties (which are therefore faster to access than a method and involve less boilerplate code). So if a readonly flag doesn't resolve that problem, and in practice makes it a bit worse, that puts it at a net-negative ROI. I keep coming back to supporting lazy-initialization because that seems to me the best way to resolve that problem, and gives us some very clean additional functionality. Are there other options that are better? Could well be. Let's enumerate them and poke holes in all of those, too.
> > 3) Cloning creates an interesting and complicated case of both of the above. Does a cloned object start with its write-once bits reset or no? There's problems both ways. > > If there are problems, let us find solutions. > > 1. Only allow internal methods to change write-once properties.
Possible; which then leads to the question "why can't every property have asymmetric access?" Which someone else upthread mentioned, and IMO warrants its own discussion.
> 2. Options: > 2a. Extend clone statement to have `clone rewrite $foo` that would > allow rewriting the write-once properties.
In the common case, you'd only want to overwrite a subset of properties, probably only one. How do you "unlock" just that one property? Is that the right way to go about it? (I honestly don't know.)
> 2b. Allow rewriting once of write-once object properties until some > operation is performed to seal or lock the object (operation being a > method call, a function call with the object as param, or a statement > with the object as argument.)
My preference here would be the "overwrite code block" Rowan suggested. That may have other knock-on effects (either for consistency or engine complexity) that we haven't thought of yet.
> 2c. Don't allow rewriting of cloned objects. See if it is really a > problem. Fix it in a future RFC if so.
Possible, but also then removes one of the primary use cases for the feature: Immutable value objects, since then evolving them becomes far more work than it is now. --Larry Garfield

Rowan Collins

6 years ago
On Fri, 21 Feb 2020 at 23:18, Larry Garfield <larry@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; } Regards,
-- Rowan Tommins [IMSoP]

Larry Garfield

6 years ago
On Mon, Feb 24, 2020, at 7:55 AM, Rowan Tommins wrote:
> On Fri, 21 Feb 2020 at 23:18, Larry Garfield <larry@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. --Larry Garfield

Nicolas Grekas

6 years ago
Le lun. 24 févr. 2020 à 21:35, Larry Garfield <larry@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 <larry@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

Máté Kocsis

6 years ago
> > I totally agree with this: there must be a way to work around the keyword - > either via reflection or another means.
Via Reflection, it could be a new method `->setWritable(true)` (next to
> `->setAccessible(true)`). >
I'm OK to support working around the keyword via reflection. Since my implementation uses a property flag to determine if a property is writable, and the flag is flipped after writing, the ->setWritable(true) wouldn't work. But I can imagine adding a ->setUninitialized() method, or adding a third parameter to ->setValue() which would affect whether the property flag should be first reset or not. Adding a separate ->setValue() method for this purpose is also possible, but I couldn't find a good name for that yet. What do you think?
> Another way, which is my current preference, would be to have visibility be > taken into consideration when using the keyword: >
To be honest, I don't agree with this idea. It is rather a simpler property accessor variant with which one can define separate - but predefined - visibility rules for reading and writing. Furthermore, I think it's worth to protect people from their own mistakes. I know for sure that at least I'd need those safeguards. :) But if we are talking about unintended changes coming from outside scopes, and if we only consider "legal" use-cases, private and protected properties are already pretty much safe. However, if we also consider possible changes coming from an inside scope, and other, "illegal" use-cases, we currently don't have any protection. And not even property accessors or your idea could help here. Given, immutability became mainstream via PSR-7 at least, I think it would be beneficial to add first-class support for this principle. I remember that one of the criticisms against the implementation of PSR-7 was that immutability is not even possible to achieve in PHP (apart from the fact that PSR-7 uses a mutable resource for storing the body). Now, we could solve the first problem, but that requires us to stay with the proposed implementation of the RFC. Regards, Máté

Mike Schinkel

6 years ago
> On Feb 24, 2020, at 8:55 AM, Rowan Tommins <rowan.collins@gmail.com> wrote: > > As with typed properties, I wonder if there's a way we can introduce....
Now you are talking! This gives me hope after my dejected last comment on the list. -Mike

Máté Kocsis

6 years ago
> > Of course, that does leave the question of how often you need one or the > other. Maybe just the asymmetric visibility is sufficient for most > practical purposes, in which case it may not be worthwhile to introduce > readonly properties as a separate feature. >
The examples shown in my previous email are indeed not very practical, but still, I would say that the added protection against possible misuse or accidental modifications (coming from either inside or outside) would be useful. Maybe it would make more sense to forbid readonly properties with default
> values? >
As I mentioned in my response to Larry, my point of view is that default values should be allowed. If there is a big opposition against this, I'm open for a change though.
> Regarding the keyword choice, I think you can drop "sealed" from the list, > as it is an established term that affects inheritance, not mutability. Of > the choices you present, "immutable", "readonly" and "writeonce" seem like > the most viable candidates. >
Thank you for the suggestions! Sure, we can drop "sealed", and I'm ok to add "immutable" and "readonly" to the list of voting choices. I'll also extend the evaluations with your thoughts. Regard, Máté