[RFC] Readonly properties

php.internals

Nikita Popov

5 years ago
Hi internals, I'd like to open the discussion on readonly properties: https://wiki.php.net/rfc/readonly_properties_v2 This proposal is similar to the https://wiki.php.net/rfc/write_once_properties RFC that has been declined previously. One significant difference is that the new RFC limits the scope of initializing assignments. I think a key mistake of the previous RFC was the confusing "write-once" framing, which is both technically correct and quite irrelevant. Please see the rationale section ( https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this proposal relates to other RFCs and alternatives. Regards, Nikita

Pierre

5 years ago
Le 04/06/2021 à 17:19, Nikita Popov a écrit :
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita >
Hello, I like this RFC, but I have a question:
> Specifying an explicit default value on readonly properties is also
not allowed: Does that mean I wouldn't be allowed to write something such as: <?php class Point {     public function __construct(         public readonly int $x = 0,         public readonly int $y = 0,     ) {} } $foo = new Point(x: 12); ?> Because the readonly+promoted+named combo is very powerful but if you can't assign defaults it makes it much less interesting. Regards,
-- Pierre

Pierre

5 years ago
Le 04/06/2021 à 17:34, Pierre a écrit :
> Le 04/06/2021 à 17:19, Nikita Popov a écrit : >> Hi internals, >> >> I'd like to open the discussion on readonly properties: >> https://wiki.php.net/rfc/readonly_properties_v2 >> >> This proposal is similar to the >> https://wiki.php.net/rfc/write_once_properties RFC that has been >> declined >> previously. One significant difference is that the new RFC limits the >> scope >> of initializing assignments. I think a key mistake of the previous >> RFC was >> the confusing "write-once" framing, which is both technically correct >> and >> quite irrelevant. >> >> Please see the rationale section ( >> https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this >> proposal relates to other RFCs and alternatives. >> >> Regards, >> Nikita >> > Hello, > > I like this RFC, but I have a question: > > > Specifying an explicit default value on readonly properties is also > not allowed: > > Does that mean I wouldn't be allowed to write something such as: > > <?php > > class Point { > >     public function __construct( > >         public readonly int $x = 0, > >         public readonly int $y = 0, > >     ) {} > > } > > > $foo = new Point(x: 12); > > ?> > > Because the readonly+promoted+named combo is very powerful but if you > can't assign defaults it makes it much less interesting. > > Regards, > > -- > > Pierre >
Please ignore my mail, the answer is just below... Sorry for the noise.
-- Pierre

Matthew Brown

5 years ago
This is a great idea! Might be worth mentioning that Psalm already supports a `@readonly` docblock annotation (first suggested by Nuno Maduro), and it matches the proposed behaviour (though Psalm doesn't currently prevent inheritance issues): Example: https://psalm.dev/r/7ed5872738 On Fri, 4 Jun 2021 at 11:19, Nikita Popov <nikita.ppv@gmail.com> wrote:

Nicolas Grekas

5 years ago
Hi Nikita, Le ven. 4 juin 2021 à 17:19, Nikita Popov <nikita.ppv@gmail.com> a écrit :
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 >
Thanks for the proposal, that's quite appealing! I voted "no" on the previous proposal because it didn't play well with cloning. That's why I favoured asymmetric visibility. I see that this new iteration has the same issue, so I'd like to know: What about putting readonly properties in the uninitialized state when cloning objects? Cheers, Nicolas

Pierre

5 years ago
Le 04/06/2021 à 17:19, Nikita Popov a écrit :
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita >
Hello, If I understood it well you count assignments, one note thought after re-reading once again, you state:
> This variant is not allowed, as the initializing assignment occurs from outside the class:
Does this mean it'll prevent programmatic object hydration using the scope-stealing closure pattern ? Such as: <?php class SomeClass {     public readonly int $foo; } $bar = 12; $object = (new \ReflectionClass('SomeClass'))->newInstanceWithoutConstructor(); (\Closure::bind(     function (SomeClass $object) ($bar         $object->foo = $bar;     },     null,     SomeClass::class ))($object); ?> I know this is a weird stuff to do I wouldn't advise to others myself, but some hydration libraries such as ocramius/generated-hydrator do leverage this mechanism to hydrate objects, and I sometime do it myself in business code, sporadically, to hydrate objects from database without needing to define a constructor for those value objects. If it does prevent such code, will there be some additional mechanism to create value objects with readonly properties without constructor ? Such as an JavaScript object-like object instanciation mechanism ? <?php class SomeClass {     public readonly int $foo; } $bar = 12; $object = {     foo: $bar, }; ?> I know this goes far outside of this RFC's own scope, but maybe this is the occasion to open a discussion about this, this would play well with readonly properties and various serializers or hydrators libraries. Regards,
-- Pierre

Deleu

5 years ago
On Sat, Jun 5, 2021, 11:47 Pierre <pierre-php@processus.org> wrote:
> Le 04/06/2021 à 17:19, Nikita Popov a écrit : > > Hi internals, > > > > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been > declined > > previously. One significant difference is that the new RFC limits the > scope > > of initializing assignments. I think a key mistake of the previous RFC > was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > > > Regards, > > Nikita > > > Hello, > > If I understood it well you count assignments, one note thought after > re-reading once again, you state: > > > This variant is not allowed, as the initializing assignment occurs from > outside the class: > > Does this mean it'll prevent programmatic object hydration using the > scope-stealing closure pattern ? Such as: > > <?php > > class SomeClass > { > public readonly int $foo; > } > > $bar = 12; > > > $object = (new > \ReflectionClass('SomeClass'))->newInstanceWithoutConstructor(); > > > (\Closure::bind( > function (SomeClass $object) ($bar > $object->foo = $bar; > }, > null, > SomeClass::class > ))($object); > > ?> > > I know this is a weird stuff to do I wouldn't advise to others myself, > but some hydration libraries such as ocramius/generated-hydrator do > leverage this mechanism to hydrate objects, and I sometime do it myself > in business code, sporadically, to hydrate objects from database without > needing to define a constructor for those value objects. > > If it does prevent such code, will there be some additional mechanism to > create value objects with readonly properties without constructor ? Such > as an JavaScript object-like object instanciation mechanism ? > > <?php > > class SomeClass > { > public readonly int $foo; > } > > $bar = 12; > > $object = { > foo: $bar, > }; > > ?> > > I know this goes far outside of this RFC's own scope, but maybe this is > the occasion to open a discussion about this, this would play well with > readonly properties and various serializers or hydrators libraries. > > Regards, > > -- > > Pierre >
Sounds like a revelant subject to be raised, indeed. But I honestly hope that this awkward hack will not prevent us from getting read-only attributes.

Nikita Popov

5 years ago
On Sat, Jun 5, 2021 at 11:47 AM Pierre <pierre-php@processus.org> wrote:
> Le 04/06/2021 à 17:19, Nikita Popov a écrit : > > Hi internals, > > I'd like to open the discussion on readonly properties:https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to thehttps://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section (https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita > > > Hello, > > If I understood it well you count assignments, one note thought after > re-reading once again, you state: > > > This variant is not allowed, as the initializing assignment occurs from outside the class: > > Does this mean it'll prevent programmatic object hydration using the > scope-stealing closure pattern ? Such as: > > <?php > > class SomeClass > { > public readonly int $foo; > } > > $bar = 12; > > > $object = (new \ReflectionClass('SomeClass'))->newInstanceWithoutConstructor(); > > > (\Closure::bind( > function (SomeClass $object) ($bar > $object->foo = $bar; > }, > null, > SomeClass::class > ))($object); > > ?> > > I know this is a weird stuff to do I wouldn't advise to others myself, but > some hydration libraries such as ocramius/generated-hydrator do leverage > this mechanism to hydrate objects, and I sometime do it myself in business > code, sporadically, to hydrate objects from database without needing to > define a constructor for those value objects. >
Yes, this is supported. What matters is the scope from which the initialization occurs. If you rebind a closure to the scope of the class, then you can perform any operations that would be legal inside that class, including access to private properties and initialization of readonly properties. Regards, Nikita

Pierre

5 years ago
Le 05/06/2021 à 12:06, Nikita Popov a écrit :
> Yes, this is supported. What matters is the scope from which the > initialization occurs. If you rebind a closure to the scope of the class, > then you can perform any operations that would be legal inside that class, > including access to private properties and initialization of readonly > properties. > > Regards, > Nikita
OK, I wasn't sure I thought I'd ask, this is nice, thank you ! I can't vote, but I love this RFC, thank you very much for this. I sincerely hope this will not get into flames.
-- Pierre

Benjamin Eberlei

5 years ago
On Fri, Jun 4, 2021 at 5:20 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. >
I have a question about Reflection support, the RFC states:
> ReflectionProperty::setValue() can bypass the requirement that
initialization occurs from the scope where the property has been declared. However, reflection cannot modify a readonly property that has already been initialized. Is there a reason why this is not possible? I am thinking about ORMs or Deserializers here where a pattern would be: class MyDataObject { public function __construct( public readonly $foo ) {} } $dataObject = $reflectionClass->newInstanceWithoutConstructor(); $dataObject->getProperty('foo')->setValue($row['value']);

Nikita Popov

5 years ago
On Sat, Jun 5, 2021 at 4:28 PM Benjamin Eberlei <kontakt@beberlei.de> wrote:
> > > On Fri, Jun 4, 2021 at 5:20 PM Nikita Popov <nikita.ppv@gmail.com> wrote: > >> Hi internals, >> >> I'd like to open the discussion on readonly properties: >> https://wiki.php.net/rfc/readonly_properties_v2 >> >> This proposal is similar to the >> https://wiki.php.net/rfc/write_once_properties RFC that has been declined >> previously. One significant difference is that the new RFC limits the >> scope >> of initializing assignments. I think a key mistake of the previous RFC was >> the confusing "write-once" framing, which is both technically correct and >> quite irrelevant. >> >> Please see the rationale section ( >> https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this >> proposal relates to other RFCs and alternatives. >> > > I have a question about Reflection support, the RFC states: > > > ReflectionProperty::setValue() can bypass the requirement that > initialization occurs from the scope where the property has been declared. > However, reflection cannot modify a readonly property that has already been > initialized. > > Is there a reason why this is not possible? I am thinking about ORMs or > Deserializers here where a pattern would be: > > class MyDataObject > { > public function __construct( > public readonly $foo > ) {} > } > > $dataObject = $reflectionClass->newInstanceWithoutConstructor(); > $dataObject->getProperty('foo')->setValue($row['value']); >
This example is compatible with the proposal. Calling newInstanceWithoutConstructor() leaves the property uninitialized, and you can initialize it through reflection (or closure rebinding) from any scope. Hydration and deserialization only requires the ability to initialize, not to change the value of properties past initialization. Regards, Nikita

Benjamin Eberlei

5 years ago
On Fri, Jun 4, 2021 at 5:20 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. >
I have a question about Reflection support, the RFC states:
> ReflectionProperty::setValue() can bypass the requirement that
initialization occurs from the scope where the property has been declared. However, reflection cannot modify a readonly property that has already been initialized. Is there a reason why this is not possible? I am thinking about ORMs or Deserializers here where a pattern would be: class MyDataObject { public function __construct( public readonly $foo ) {} } $dataObject = $reflectionClass->newInstanceWithoutConstructor(); $dataObject->getProperty('foo')->setValue($row['value']);

Larry Garfield

5 years ago
On Fri, Jun 4, 2021, at 10:19 AM, Nikita Popov wrote:
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. > > Regards, > Nikita
Thank you for the detailed analysis in the rationale section. I am, however, still skeptical of this approach, for a couple of reasons. 1. This does appear to address the "leakage" problem I noted in my earlier analysis around the start of the year, when considering the writeonce proposal[1][2]. That's great to see. 2. It doesn't address the larger question of cloning, however. The answer for now seems "maybe we'll get clone-with at some point", which would be a way around it, but there is no active RFC for that right now. I'm obviously very in favor of RFCs that complement each other to give more than the sum of their parts, but those RFCs need to be at least on the horizon to actually come together. Right now that looks like it won't happen this cycle. Absent clone-with, readonly would be effectively unusable in any evolvable object of any non-trivial complexity. It also wouldn't work with objects that need properties that are not constructor arguments, such as PSR-7 type objects. That's a no in my book. 3. As noted in my previous analysis, even with clone-with, asymmetric visibility results in a nicer syntax when evolving objects that have any complexity to them. (See the examples in [2].) 4. One detail left out of the rationale section is how much of a performance difference there is between readonly and implicit-accessors. It says the latter still has a performance hit, but not how much. How significant is it? If it's tiny, then frankly the biggest argument for readonly goes away, since implicit-accessors gives us 98% the same functionality in a more forward-compatible way without the cloning issues. If it's twice as slow, then having a separate keyword for a common case makes sense. 5. I would have to experiment a bit with hydration, as others have noted, because unconventional object construction mechanisms are a critically important workflow. The RFC even notes in passing that serialization is possibly made complicated. Just how complicated? There's no mention of __deserialize() and how it would interact, but cases like that need to be very clearly handled and documented. 6. I'm OK with the approach to constructor promotion and default values. That seems reasonable in context, especially if combined with New-in-initializers[3], which I am hoping you plan to finish this cycle. :-) 7. Though, it just occurred to me, this may result in issues around optional values. class URL { public function __construct( public readonly string $scheme = 'https', public readonly string $domain = '', public readonly int $port = '443', public readonly string $path = '', ) {} } Now, if you want to hydrate the object externally, you need to ensure those properties are not set by the constructor. However, a promoted value is always set by the constructor; either it has a default or it is required. From previous replies it sounds like the workaround for that is reflection and newWithoutConstructor(), but I'm not sure how I feel about that, because that would also then preclude any *other* logic in the constructor, as that would also get skipped. Perhaps this isn't as big of an issue as I think it is, and if so I'd love to hear why, but it makes me concerned. 8. Although the RFC says it does not preclude accessors or explicit asymmetric visibility in the future, and I absolutely believe that Nikita is genuine about that, I am still concerned that should more robust proposals come forward later, it will be met with "we don't need another still-fancier syntax here, readonly is good enough." It's good to know that C# manages to have both, but that doesn't mean the same logic would apply in PHP, or to PHP voters, specifically. 9. I know that the variance for properties in child classes was a source of discussion, and the end result seems to be that readonly-ness is invariant. However, that precludes having an easy way to have both a mutable and immutable version of a class, easily. (If you wanted to have, say, a read-only class most of the time for safety, but for writing you use an alternate "open" version that can be updated and then persisted to the database.) That's a style I've experimented with on and off for a while and already don't have a great solution to, but it feels like readonly would make that even harder. Again, I'd be very happy to hear alternatives around that. Depending on the answers to the above, I could be convinced of this as a stop gap iff paired with clone-with in the same version. However, on its own I think this is only a half-solution, and I'm not wild about a half-solution for a version or two. That's why I prefer going straight to asymmetric visibility, as that would cover mostly the same use case in a single RFC while still being forward compatible; it would benefit from clone-with, but still be usable without it. [1] https://peakd.com/hive-168588/@crell/object-properties-and-immutability [2] https://peakd.com/hive-168588/@crell/object-properties-part-2-examples [3] https://wiki.php.net/rfc/new_in_initializers --Larry Garfield

Mike Schinkel

5 years ago
> On Jun 5, 2021, at 12:51 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Fri, Jun 4, 2021, at 10:19 AM, Nikita Popov wrote: >> Hi internals, >> >> I'd like to open the discussion on readonly properties: >> https://wiki.php.net/rfc/readonly_properties_v2 >> >> This proposal is similar to the >> https://wiki.php.net/rfc/write_once_properties RFC that has been declined >> previously. One significant difference is that the new RFC limits the scope >> of initializing assignments. I think a key mistake of the previous RFC was >> the confusing "write-once" framing, which is both technically correct and >> quite irrelevant. >> >> Please see the rationale section ( >> https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this >> proposal relates to other RFCs and alternatives. >> >> Regards, >> Nikita > > Thank you for the detailed analysis in the rationale section. I am, however, still skeptical of this approach, for a couple of reasons. > > 1. This does appear to address the "leakage" problem I noted in my earlier analysis around the start of the year, when considering the writeonce proposal[1][2]. That's great to see. > > 2. It doesn't address the larger question of cloning, however. The answer for now seems "maybe we'll get clone-with at some point", which would be a way around it, but there is no active RFC for that right now. I'm obviously very in favor of RFCs that complement each other to give more than the sum of their parts, but those RFCs need to be at least on the horizon to actually come together. Right now that looks like it won't happen this cycle. Absent clone-with, readonly would be effectively unusable in any evolvable object of any non-trivial complexity. It also wouldn't work with objects that need properties that are not constructor arguments, such as PSR-7 type objects. That's a no in my book.
Larry, To address the cloning objection to raise to Nikita's RFC there is one solution you did not mention in your blog post[1. Rather than your clone-with using a syntax that does not exist anywhere else[2] in PHP we could instead use one similar to your preferred "clone-with" approach where a closure could be run in an internal context. This closure would give the best of both your clone-with and of __clone() approaches. Applying this approach to your example in [3] gives us: $r6 = clone $r5 with function($obj) { $obj->uri = new Uri('https://python.org/'); $obj->headers = ['host' => 'http://java.com/']; $obj->version = 'the old one'; }; If PHP Internals were to approve the "Auto-capturing multi-statement closures" RFC[4] we could also have auto-capture in a place where it would be likely common: $uri = new Uri('https://python.org/'); $headers = [host => 'http://java.com/']; $version = 'the old one'; $r6 = clone $r5 with fn($obj) { $obj->uri = $uri; $obj->headers = $headers; $obj->version = $version; }; Another improvement, which I don't know if it is possible or even advisable, is if the closure could have automatic access to a $this variable for the instance of the cloned object then it becomes even simpler: $uri = new Uri('https://python.org/'); $headers = [host: 'http://java.com/']; $version = 'the old one'; $r6 = clone $r5 with fn() { $this->uri = $uri; $this->headers = $headers; $this->version = $version; }; Interestingly, using your example[2] again, if we were to adopt the same syntax for `new` statements we could obviate the need for tedious and verbose "wither" methods as well as the numerous internal clones and throwaway variables needed with withers called immediately after instantiation. For example: $r2 = new Request() with fn() { $this->method = 'POST'; $this->uri = new Uri('https://php.net/'); $this->protocolVersion = '2.0'; $this->header = [ 'cache' => 'none' ]; }; Lastly, you proposed "with" as your keyword to identify the initialization values which I assume you picked it because of the "wither" method pattern, and my examples above followed suit. However, given `with` is not currently a reserved word in PHP and using a closure inherently provides an existing keyword, PHP could leverage `function` and/or `fn` to indicate "with." (Note the following example do not include the two (2) enhancements mentioned above using fn and $this): $r6 = clone $r5 function($obj) {...}; -Mike [1] https://peakd.com/hive-168588/@crell/object-properties-and-immutability <https://peakd.com/hive-168588/@crell/object-properties-and-immutability> [2] Even though I would very much like to see that syntax become a general purpose syntax for object instantiation, I think it would not be good to use it unless it were fully fleshed out for much greater utility. [3] https://peakd.com/hive-168588/@crell/object-properties-part-2-examples <https://peakd.com/hive-168588/@crell/object-properties-part-2-examples> [4] https://wiki.php.net/rfc/auto-capture-closure <https://wiki.php.net/rfc/auto-capture-closure>

Nikita Popov

5 years ago
On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Fri, Jun 4, 2021, at 10:19 AM, Nikita Popov wrote: > > Hi internals, > > > > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been > declined > > previously. One significant difference is that the new RFC limits the > scope > > of initializing assignments. I think a key mistake of the previous RFC > was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > > > Regards, > > Nikita > > Thank you for the detailed analysis in the rationale section. I am, > however, still skeptical of this approach, for a couple of reasons. > > 1. This does appear to address the "leakage" problem I noted in my earlier > analysis around the start of the year, when considering the writeonce > proposal[1][2]. That's great to see. > > 2. It doesn't address the larger question of cloning, however. The answer > for now seems "maybe we'll get clone-with at some point", which would be a > way around it, but there is no active RFC for that right now. I'm > obviously very in favor of RFCs that complement each other to give more > than the sum of their parts, but those RFCs need to be at least on the > horizon to actually come together. Right now that looks like it won't > happen this cycle. Absent clone-with, readonly would be effectively > unusable in any evolvable object of any non-trivial complexity.
Compared to the general area of applicability of readonly properties, the cases that require clone-with are rare. It's the intersection of "not-really-immutable objects using wither evolution" and "has so many properties that passing them to the constructor is infeasible". While this intersection does include a couple of prominent examples, they are by no means numerous. While it may be a problem worth solving, I *personally* don't consider it neither particularly important nor time critical. For what it's worth, I believe Mate wants to work on clone-with once this RFC passes -- while there is no RFC for that, there is already an implementation, though from a cursory look it would require some adjustments to actually work with readonly properties.
> It also wouldn't work with objects that need properties that are not > constructor arguments, such as PSR-7 type objects. That's a no in my book. >
I don't understand what you mean here. Why would properties that are not constructor arguments be a problem?
> 3. As noted in my previous analysis, even with clone-with, asymmetric > visibility results in a nicer syntax when evolving objects that have any > complexity to them. (See the examples in [2].) >
I tend to agree. And I am not opposed to having asymmetric visibility as well. For example, C# does both, and I think both do have value. "readonly" is a very strong signal to the reader. Asymmetric visibility is not. Asymmetric visibility (without proper readonly support) could mean that the property is actually readonly, or it could mean that it's exactly what it says on the tin: The property is internally mutable. I don't think these two concepts should be conflated, though they certainly have overlap.
> 4. One detail left out of the rationale section is how much of a > performance difference there is between readonly and implicit-accessors. > It says the latter still has a performance hit, but not how much. How > significant is it? If it's tiny, then frankly the biggest argument for > readonly goes away, since implicit-accessors gives us 98% the same > functionality in a more forward-compatible way without the cloning issues. > If it's twice as slow, then having a separate keyword for a common case > makes sense. >
The performance difference shouldn't be large, maybe 10-15%. I don't consider performance a big deciding factor here. When comparing to accessors the relevant difference I see is that readonly properties cover a large use case at a small fraction of language complexity.
> 5. I would have to experiment a bit with hydration, as others have noted, > because unconventional object construction mechanisms are a critically > important workflow. The RFC even notes in passing that serialization is > possibly made complicated. Just how complicated? There's no mention of > __deserialize() and how it would interact, but cases like that need to be > very clearly handled and documented. >
The RFC notes in passing that *if* readonly default values were allowed, it could have impact on userland deserialization. As-is, I believe the proposal doesn't present any problem for userland hydrators or deserializers, or PHP's own unserialization mechanisms. This should get mentioned in the proposal, though it will be along the lines of "yes, everything works as expected".
> 6. I'm OK with the approach to constructor promotion and default values. > That seems reasonable in context, especially if combined with > New-in-initializers[3], which I am hoping you plan to finish this cycle. :-) > > 7. Though, it just occurred to me, this may result in issues around > optional values. > > class URL { > public function __construct( > public readonly string $scheme = 'https', > public readonly string $domain = '', > public readonly int $port = '443', > public readonly string $path = '', > ) {} > } > > Now, if you want to hydrate the object externally, you need to ensure > those properties are not set by the constructor. However, a promoted value > is always set by the constructor; either it has a default or it is > required. From previous replies it sounds like the workaround for that is > reflection and newWithoutConstructor(), but I'm not sure how I feel about > that, because that would also then preclude any *other* logic in the > constructor, as that would also get skipped. Perhaps this isn't as big of > an issue as I think it is, and if so I'd love to hear why, but it makes me > concerned. >
Using newInstanceWithoutConstructor() is not a workaround, to the best of my knowledge these things are always implemented this way. You let the constructor initialize the properties, or you bypass the constructor and manually initialize them. I don't really see how it could work in any other way, unless you have some very specific contract between your constructor and a hydrator library (in which case you should make that contract with a method other than the constructor). 8. Although the RFC says it does not preclude accessors or explicit
> asymmetric visibility in the future, and I absolutely believe that Nikita > is genuine about that, I am still concerned that should more robust > proposals come forward later, it will be met with "we don't need another > still-fancier syntax here, readonly is good enough." It's good to know > that C# manages to have both, but that doesn't mean the same logic would > apply in PHP, or to PHP voters, specifically. >
To clarify, I think your argument here goes roughly like this (correct me if I'm wrong): If we already have one of readonly properties or asymmetric visibility, an argument could be made for not adding the other one because it doesn't provide sufficient marginal value. So, if we assume that we can only have one of these features, is readonly properties the right one to add? Shouldn't we add asymmetric visibility instead, which can be used for a wider range of use cases? I think this is a legitimate concern to have, but (under the assumption that we can have only one) it's not easy to say which would be preferable. Yes, asymmetric visibility is more widely applicable, but at the same time I would expect most applications to be bonafide readonly properties. So while asymmetric visibility is more widely applicable, it's also imprecise for the majority use case. Which is better? Of course, my assumption here could also be wrong. 9. I know that the variance for properties in child classes was a source of
> discussion, and the end result seems to be that readonly-ness is > invariant. However, that precludes having an easy way to have both a > mutable and immutable version of a class, easily. (If you wanted to have, > say, a read-only class most of the time for safety, but for writing you use > an alternate "open" version that can be updated and then persisted to the > database.) That's a style I've experimented with on and off for a while > and already don't have a great solution to, but it feels like readonly > would make that even harder. Again, I'd be very happy to hear alternatives > around that. >
I don't believe you can't have an immutable class extend a mutable one or a mutable extend an immutable one, both result in LSP violations (though not on the type level). There was a lot of discussion around this when DateTimeImmutable was introduced, which notably is not in an inheritance relationship (in other direction) with DateTime, for good reason.
> Depending on the answers to the above, I could be convinced of this as a > stop gap iff paired with clone-with in the same version. However, on its > own I think this is only a half-solution, and I'm not wild about a > half-solution for a version or two. That's why I prefer going straight to > asymmetric visibility, as that would cover mostly the same use case in a > single RFC while still being forward compatible; it would benefit from > clone-with, but still be usable without it. >
Just to reiterate this point: Readonly properties is not a half solution to which asymmetric visibility is the full solution. They are *different* solutions to different albeit overlapping problems. I'm not proposing readonly properties here because it's the only thing I can get into 8.1, I'm proposing it because I think it's the more important problem to solve. Regards, Nikita

Larry Garfield

5 years ago
On Mon, Jun 7, 2021, at 4:06 AM, Nikita Popov wrote:
> On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield <larry@garfieldtech.com> > wrote:
> > Thank you for the detailed analysis in the rationale section. I am, > > however, still skeptical of this approach, for a couple of reasons. > > > > 1. This does appear to address the "leakage" problem I noted in my earlier > > analysis around the start of the year, when considering the writeonce > > proposal[1][2]. That's great to see. > > > > 2. It doesn't address the larger question of cloning, however. The answer > > for now seems "maybe we'll get clone-with at some point", which would be a > > way around it, but there is no active RFC for that right now. I'm > > obviously very in favor of RFCs that complement each other to give more > > than the sum of their parts, but those RFCs need to be at least on the > > horizon to actually come together. Right now that looks like it won't > > happen this cycle. Absent clone-with, readonly would be effectively > > unusable in any evolvable object of any non-trivial complexity. > > > Compared to the general area of applicability of readonly properties, the > cases that require clone-with are rare. It's the intersection of > "not-really-immutable objects using wither evolution" and "has so many > properties that passing them to the constructor is infeasible". While this > intersection does include a couple of prominent examples, they are by no > means numerous. While it may be a problem worth solving, I *personally* > don't consider it neither particularly important nor time critical. > > For what it's worth, I believe Mate wants to work on clone-with once this > RFC passes -- while there is no RFC for that, there is already an > implementation, though from a cursory look it would require some > adjustments to actually work with readonly properties.
I look forward to it. I don't think the use cases for non-trivial structs (where a withX() method can just return new static(list all properties here) ) are as few as you think. One difficulty in determining that is that, if they are made easier/more robust (via any of the mechanisms under discussion), their usage is likely to increase. That's a good thing, and a benefit of all of these pieces floating around, but it does make it harder to gauge the net-use of any particular feature. I prefer to err on the side of enabling more flexibility and power rather than less.
> > It also wouldn't work with objects that need properties that are not > > constructor arguments, such as PSR-7 type objects. That's a no in my book. > > > > I don't understand what you mean here. Why would properties that are not > constructor arguments be a problem?
Without clone-with, if you wanted a with-er method, it would necessarily look like this: class Point { public function __construct( public readonly int $x, public readonly int $y, public readonly int $z, ) {} public function withX(int $newX) { return new static($newX, $this->y, $this->z); } } That is, you're populating the entire property set via the constructor. In some cases that's fine; the larger the object, though, the more needlessly verbose redundancy that has. Now let's add a "rendered" flag property, that is public readonly bool. (Maybe not the best example; it's before 9 am and I'm trying to think on the fly here.) It's not in the constructor, but for later processing. It would get set as a side effect of some other method, to indicate the point has already been drawn. Now make a new version of the object with a different Z. What do you do with $rendered?
> > 3. As noted in my previous analysis, even with clone-with, asymmetric > > visibility results in a nicer syntax when evolving objects that have any > > complexity to them. (See the examples in [2].) > > > > I tend to agree. And I am not opposed to having asymmetric visibility as > well. For example, C# does both, and I think both do have value. "readonly" > is a very strong signal to the reader. Asymmetric visibility is not. > Asymmetric visibility (without proper readonly support) could mean that the > property is actually readonly, or it could mean that it's exactly what it > says on the tin: The property is internally mutable. I don't think these > two concepts should be conflated, though they certainly have overlap.
I think this is the crux of the disagreement. There's a large swath of cases that could be well-handled by either. I feel the cases that are only or better handled by asymmetric visibility is larger than the cases that are only or better handled by readonly. As noted above, I don't know that either of us have any data with which to determine which is actually the case. (Maybe someone with more C# experience can speak to that? What's typical there, where both mechanisms are available?)
> > 5. I would have to experiment a bit with hydration, as others have noted, > > because unconventional object construction mechanisms are a critically > > important workflow. The RFC even notes in passing that serialization is > > possibly made complicated. Just how complicated? There's no mention of > > __deserialize() and how it would interact, but cases like that need to be > > very clearly handled and documented. > > > > The RFC notes in passing that *if* readonly default values were allowed, it > could have impact on userland deserialization. As-is, I believe the > proposal doesn't present any problem for userland hydrators or > deserializers, or PHP's own unserialization mechanisms. This should get > mentioned in the proposal, though it will be along the lines of "yes, > everything works as expected". > > > > 6. I'm OK with the approach to constructor promotion and default values. > > That seems reasonable in context, especially if combined with > > New-in-initializers[3], which I am hoping you plan to finish this cycle. :-)
> 8. Although the RFC says it does not preclude accessors or explicit > > asymmetric visibility in the future, and I absolutely believe that Nikita > > is genuine about that, I am still concerned that should more robust > > proposals come forward later, it will be met with "we don't need another > > still-fancier syntax here, readonly is good enough." It's good to know > > that C# manages to have both, but that doesn't mean the same logic would > > apply in PHP, or to PHP voters, specifically. > > > > To clarify, I think your argument here goes roughly like this (correct me > if I'm wrong): If we already have one of readonly properties or asymmetric > visibility, an argument could be made for not adding the other one because > it doesn't provide sufficient marginal value. So, if we assume that we can > only have one of these features, is readonly properties the right one to > add? Shouldn't we add asymmetric visibility instead, which can be used for > a wider range of use cases?
That is essentially correct. I know you're not making that argument, and I'm not making that argument, but it's an argument I can absolutely see being made, because it has been in the past for other things, and sometimes I agree with it as it can be a valid argument. Hence why, in general, I prefer to go with the more robust, more "interacts nicely with other things" option from the get-go, to both not make more robust options harder to adopt in the future and to avoid user confusion down the road when they have 3 different tools that all do *mostly* the same thing and aren't sure of the subtle reasons why they'd use one over the other. (Like, for example, if one had a readonly property but wanted to convert it to a custom accessor property... what are the subtle knock on effects they have to account for, then? There are almost certainly more of them than if switching a property from implicit accessors to explicit accessors, although off hand I don't know what they would all be.)
> I think this is a legitimate concern to have, but (under the assumption > that we can have only one) it's not easy to say which would be preferable. > Yes, asymmetric visibility is more widely applicable, but at the same time > I would expect most applications to be bonafide readonly properties. So > while asymmetric visibility is more widely applicable, it's also imprecise > for the majority use case. Which is better? Of course, my assumption here > could also be wrong.
As above, I'd love to get some actual data from the C# community on this. Lacking that, we're all just guessing.
> 9. I know that the variance for properties in child classes was a source of > > discussion, and the end result seems to be that readonly-ness is > > invariant. However, that precludes having an easy way to have both a > > mutable and immutable version of a class, easily. (If you wanted to have, > > say, a read-only class most of the time for safety, but for writing you use > > an alternate "open" version that can be updated and then persisted to the > > database.) That's a style I've experimented with on and off for a while > > and already don't have a great solution to, but it feels like readonly > > would make that even harder. Again, I'd be very happy to hear alternatives > > around that. > > > > I don't believe you can't have an immutable class extend a mutable one or a > mutable extend an immutable one, both result in LSP violations (though not > on the type level). There was a lot of discussion around this when > DateTimeImmutable was introduced, which notably is not in an inheritance > relationship (in other direction) with DateTime, for good reason.
True. Might that be feasible with asymmetric visibility, however? (I'm honestly not sure.)
> > Depending on the answers to the above, I could be convinced of this as a > > stop gap iff paired with clone-with in the same version. However, on its > > own I think this is only a half-solution, and I'm not wild about a > > half-solution for a version or two. That's why I prefer going straight to > > asymmetric visibility, as that would cover mostly the same use case in a > > single RFC while still being forward compatible; it would benefit from > > clone-with, but still be usable without it. > > > > Just to reiterate this point: Readonly properties is not a half solution to > which asymmetric visibility is the full solution. They are *different* > solutions to different albeit overlapping problems. I'm not proposing > readonly properties here because it's the only thing I can get into 8.1, > I'm proposing it because I think it's the more important problem to solve.
I was perhaps unclear here. I don't think readonly is a half-solution and asymmetric visibility is the full solution. I think readonly without clone-with is a half-solution. readonly plus clone-with is a mostly-full solution that ends up in a very similar (competing?) place with asymmetric visibility, which is a mostly-full solution. I can probably still vote for readonly plus clone-with, because that combination would be useful. It's readonly without clone-with that I am concerned about being a half-solution. (clone-with is a nice-to-have with asymmetric visibility, but IMO is an ergonomic necessity with readonly.) --Larry Garfield

Nikita Popov

5 years ago
On Mon, Jun 7, 2021 at 4:09 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Mon, Jun 7, 2021, at 4:06 AM, Nikita Popov wrote: > > On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield <larry@garfieldtech.com> > > wrote: > > > > Thank you for the detailed analysis in the rationale section. I am, > > > however, still skeptical of this approach, for a couple of reasons. > > > > > > 1. This does appear to address the "leakage" problem I noted in my > earlier > > > analysis around the start of the year, when considering the writeonce > > > proposal[1][2]. That's great to see. > > > > > > 2. It doesn't address the larger question of cloning, however. The > answer > > > for now seems "maybe we'll get clone-with at some point", which would > be a > > > way around it, but there is no active RFC for that right now. I'm > > > obviously very in favor of RFCs that complement each other to give more > > > than the sum of their parts, but those RFCs need to be at least on the > > > horizon to actually come together. Right now that looks like it won't > > > happen this cycle. Absent clone-with, readonly would be effectively > > > unusable in any evolvable object of any non-trivial complexity. > > > > > > Compared to the general area of applicability of readonly properties, the > > cases that require clone-with are rare. It's the intersection of > > "not-really-immutable objects using wither evolution" and "has so many > > properties that passing them to the constructor is infeasible". While > this > > intersection does include a couple of prominent examples, they are by no > > means numerous. While it may be a problem worth solving, I *personally* > > don't consider it neither particularly important nor time critical. > > > > For what it's worth, I believe Mate wants to work on clone-with once this > > RFC passes -- while there is no RFC for that, there is already an > > implementation, though from a cursory look it would require some > > adjustments to actually work with readonly properties. > > I look forward to it. > > I don't think the use cases for non-trivial structs (where a withX() > method can just return new static(list all properties here) ) are as few as > you think. One difficulty in determining that is that, if they are made > easier/more robust (via any of the mechanisms under discussion), their > usage is likely to increase. That's a good thing, and a benefit of all of > these pieces floating around, but it does make it harder to gauge the > net-use of any particular feature. I prefer to err on the side of enabling > more flexibility and power rather than less. > > > > It also wouldn't work with objects that need properties that are not > > > constructor arguments, such as PSR-7 type objects. That's a no in my > book. > > > > > > > I don't understand what you mean here. Why would properties that are not > > constructor arguments be a problem? > > Without clone-with, if you wanted a with-er method, it would necessarily > look like this: > > class Point { > public function __construct( > public readonly int $x, > public readonly int $y, > public readonly int $z, > ) {} > > public function withX(int $newX) { > return new static($newX, $this->y, $this->z); > } > } > > That is, you're populating the entire property set via the constructor. > In some cases that's fine; the larger the object, though, the more > needlessly verbose redundancy that has. > > Now let's add a "rendered" flag property, that is public readonly bool. > (Maybe not the best example; it's before 9 am and I'm trying to think on > the fly here.) It's not in the constructor, but for later processing. It > would get set as a side effect of some other method, to indicate the point > has already been drawn. Now make a new version of the object with a > different Z. What do you do with $rendered? > > > > 3. As noted in my previous analysis, even with clone-with, asymmetric > > > visibility results in a nicer syntax when evolving objects that have > any > > > complexity to them. (See the examples in [2].) > > > > > > > I tend to agree. And I am not opposed to having asymmetric visibility as > > well. For example, C# does both, and I think both do have value. > "readonly" > > is a very strong signal to the reader. Asymmetric visibility is not. > > Asymmetric visibility (without proper readonly support) could mean that > the > > property is actually readonly, or it could mean that it's exactly what it > > says on the tin: The property is internally mutable. I don't think these > > two concepts should be conflated, though they certainly have overlap. > > I think this is the crux of the disagreement. There's a large swath of > cases that could be well-handled by either. I feel the cases that are only > or better handled by asymmetric visibility is larger than the cases that > are only or better handled by readonly. As noted above, I don't know that > either of us have any data with which to determine which is actually the > case. > > (Maybe someone with more C# experience can speak to that? What's typical > there, where both mechanisms are available?) > > > > 5. I would have to experiment a bit with hydration, as others have > noted, > > > because unconventional object construction mechanisms are a critically > > > important workflow. The RFC even notes in passing that serialization > is > > > possibly made complicated. Just how complicated? There's no mention > of > > > __deserialize() and how it would interact, but cases like that need to > be > > > very clearly handled and documented. > > > > > > > The RFC notes in passing that *if* readonly default values were allowed, > it > > could have impact on userland deserialization. As-is, I believe the > > proposal doesn't present any problem for userland hydrators or > > deserializers, or PHP's own unserialization mechanisms. This should get > > mentioned in the proposal, though it will be along the lines of "yes, > > everything works as expected". > > > > > > > 6. I'm OK with the approach to constructor promotion and default > values. > > > That seems reasonable in context, especially if combined with > > > New-in-initializers[3], which I am hoping you plan to finish this > cycle. :-) > > > 8. Although the RFC says it does not preclude accessors or explicit > > > asymmetric visibility in the future, and I absolutely believe that > Nikita > > > is genuine about that, I am still concerned that should more robust > > > proposals come forward later, it will be met with "we don't need > another > > > still-fancier syntax here, readonly is good enough." It's good to know > > > that C# manages to have both, but that doesn't mean the same logic > would > > > apply in PHP, or to PHP voters, specifically. > > > > > > > To clarify, I think your argument here goes roughly like this (correct me > > if I'm wrong): If we already have one of readonly properties or > asymmetric > > visibility, an argument could be made for not adding the other one > because > > it doesn't provide sufficient marginal value. So, if we assume that we > can > > only have one of these features, is readonly properties the right one to > > add? Shouldn't we add asymmetric visibility instead, which can be used > for > > a wider range of use cases? > > That is essentially correct. I know you're not making that argument, and > I'm not making that argument, but it's an argument I can absolutely see > being made, because it has been in the past for other things, and sometimes > I agree with it as it can be a valid argument. > > Hence why, in general, I prefer to go with the more robust, more > "interacts nicely with other things" option from the get-go, to both not > make more robust options harder to adopt in the future and to avoid user > confusion down the road when they have 3 different tools that all do > *mostly* the same thing and aren't sure of the subtle reasons why they'd > use one over the other. > > (Like, for example, if one had a readonly property but wanted to convert > it to a custom accessor property... what are the subtle knock on effects > they have to account for, then? There are almost certainly more of them > than if switching a property from implicit accessors to explicit accessors, > although off hand I don't know what they would all be.) > > > I think this is a legitimate concern to have, but (under the assumption > > that we can have only one) it's not easy to say which would be > preferable. > > Yes, asymmetric visibility is more widely applicable, but at the same > time > > I would expect most applications to be bonafide readonly properties. So > > while asymmetric visibility is more widely applicable, it's also > imprecise > > for the majority use case. Which is better? Of course, my assumption here > > could also be wrong. > > As above, I'd love to get some actual data from the C# community on this. > Lacking that, we're all just guessing.>
Well, I think we can get an approximation like this: https://beta.grep.app/search?q=%7B%20get%3B%20private%20set%3B%20%7D&filter[lang][0]=C%23 { get; private set; } 74,281 results https://beta.grep.app/search?q=%7B%20get%3B%20%7D&filter[lang][0]=C%23 { get; } 156,304 results https://beta.grep.app/search?q=readonly&filter[lang][0]=C%23 readonly 409,585 results Note that the "{ get; }" part includes both readonly properties and get-only abstract properties, so that one is an overestimate. Using variants like \{\s+get;\s+private set;\s+\} doesn't change the results materially. Unless my methodology is completely borked, the ratio of readonly to asymmetric visibility seems to be something like 4:1 even as a conservative estimate. Regards, Nikita

Larry Garfield

5 years ago
On Tue, Jun 8, 2021, at 4:18 AM, Nikita Popov wrote:
> > (Like, for example, if one had a readonly property but wanted to convert > > it to a custom accessor property... what are the subtle knock on effects > > they have to account for, then? There are almost certainly more of them > > than if switching a property from implicit accessors to explicit accessors, > > although off hand I don't know what they would all be.) > > > > > I think this is a legitimate concern to have, but (under the assumption > > > that we can have only one) it's not easy to say which would be > > preferable. > > > Yes, asymmetric visibility is more widely applicable, but at the same > > time > > > I would expect most applications to be bonafide readonly properties. So > > > while asymmetric visibility is more widely applicable, it's also > > imprecise > > > for the majority use case. Which is better? Of course, my assumption here > > > could also be wrong. > > > > As above, I'd love to get some actual data from the C# community on this. > > Lacking that, we're all just guessing.> > > > Well, I think we can get an approximation like this: > > https://beta.grep.app/search?q=%7B%20get%3B%20private%20set%3B%20%7D&filter[lang][0]=C%23 > { get; private set; } 74,281 results > https://beta.grep.app/search?q=%7B%20get%3B%20%7D&filter[lang][0]=C%23 { > get; } 156,304 results > https://beta.grep.app/search?q=readonly&filter[lang][0]=C%23 readonly > 409,585 results > > Note that the "{ get; }" part includes both readonly properties and > get-only abstract properties, so that one is an overestimate. Using > variants like \{\s+get;\s+private set;\s+\} doesn't change the results > materially. > > Unless my methodology is completely borked, the ratio of readonly to > asymmetric visibility seems to be something like 4:1 even as a conservative > estimate. > > Regards, > Nikita
Interesting. I have no particular grounds to challenge your methodology, so I'll accept that C# seems to use readonly much more than asymmetric visibility/accessors. Does C# have a clone-with or equivalent? Even with readonly being useful, I think it's only half the answer without a clean evolvability mechanism beyond "re-type all the fields by hand." CPP removed a ton of that boilerplate, and I'd hate to bring it back. --Larry Garfield

Mike Schinkel

5 years ago
> On Jun 8, 2021, at 5:18 AM, Nikita Popov <nikita.ppv@gmail.com> wrote: > > Well, I think we can get an approximation like this: > > https://beta.grep.app/search?q=%7B%20get%3B%20private%20set%3B%20%7D&filter[lang][0]=C%23 <https://beta.grep.app/search?q={%20get;%20private%20set;%20}&filter[lang][0]=C#> > { get; private set; } 74,281 results > https://beta.grep.app/search?q=%7B%20get%3B%20%7D&filter[lang][0]=C%23 <https://beta.grep.app/search?q={%20get;%20}&filter[lang][0]=C#> { > get; } 156,304 results > https://beta.grep.app/search?q=readonly&filter[lang][0]=C%23 <https://beta.grep.app/search?q=readonly&filter[lang][0]=C#> readonly > 409,585 results > > Note that the "{ get; }" part includes both readonly properties and > get-only abstract properties, so that one is an overestimate. Using > variants like \{\s+get;\s+private set;\s+\} doesn't change the results > materially. > > Unless my methodology is completely borked, the ratio of readonly to > asymmetric visibility seems to be something like 4:1 even as a conservative > estimate.
When you two are speaking of "asymmetric visibility," are you speaking of this RFC[1], this one[2], one I am not mentioning, or one that does not exist yet? If you are referring to [1] there is more to asymmetric visibility than what C# would call "auto-implemented properties[3]." If we search for `get {` we find 266,859 occurrences and another 5765 for `{ set{`. I assume `readonly` would not address either of these? https://beta.grep.app/search?current=2&q=get%5Cs%2A%5C%7B&regexp=true&filter[lang][0]=C%23 <https://beta.grep.app/search?current=2&q=get%5Cs%2A%5C%7B&regexp=true&filter%5Blang%5D%5B0%5D=C%23> https://beta.grep.app/search?q=%5C%7B%5Cs%2Aset%5Cs%2A%5C%7B&regexp=true&format=e&filter[lang][0]=C%23 <https://beta.grep.app/search?q=%5C%7B%5Cs%2Aset%5Cs%2A%5C%7B&regexp=true&format=e&filter%5Blang%5D%5B0%5D=C%23> -Mike [1] https://wiki.php.net/rfc/property_accessors <https://wiki.php.net/rfc/property_accessors> [2] https://wiki.php.net/rfc/property_write_visibility <https://wiki.php.net/rfc/property_write_visibility> [3] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties <https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties> P.S. You also over-counted `readonly` by 44k. If you go with case-sensitive for "readonly" you only get 365k. Seems they like using "ReadOnly" as part of symbol names. https://beta.grep.app/search?q=readonly&case=true&filter[lang][0]=C%23 <https://beta.grep.app/search?q=readonly&case=true&filter%5Blang%5D%5B0%5D=C%23> P.P.S. I'm not taking a position pro- or con- the readonly RFC, just wanting to clarify the stats used by others to evaluate.

Pierre Joye

5 years ago
Hi Nikita, On Fri, Jun 4, 2021 at 10:19 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> > Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2
Very good work, thank you :)
> This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives.
About this paragraph in https://wiki.php.net/rfc/readonly_properties_v2#rationale: " The addition of readonly properties neither precludes nor discourages the addition of asymmetric property visibility." To me this sentence is the meaning of a readonly property, not an immutable property, as in writeable once property, in constructor. This RFC is perfect given its goal but the property attribute "readonly". How would you define a property only readable from outside the scope but writeable inside the scope of its class (->status f.e.)? This is PHP and we always have our ways. That being said, the keyword "readonly" is really about what is defined as "asymmetric property visibility" in many other languages. Rust uses annotation, .net via either readonly and they use an init setter to make it immutable in v9 (public DateTime RecordedAt { get; init; }), other like java or scala relies on setting a property getters only. Given this, I wonder if it would not be easier to have actual per property getter/setter as an addition or replacement to the current (kind of horrible) get/set($name, $value). This would all cases in one shot, a more complicated shot but much more powerful. For the record, I am totally not a fan of the 'manual" get/setMyProperty ;) In any case, I would at least put a second thought on the name "readonly". ps: sorry if this question has been discussed in previous RFCs, pointers appreciated as I did not find it :) Best,
-- Pierre

Larry Garfield

5 years ago
On Wed, Jun 9, 2021, at 5:05 AM, Pierre Joye wrote:
> Hi Nikita, > > On Fri, Jun 4, 2021 at 10:19 PM Nikita Popov <nikita.ppv@gmail.com> wrote: > > > > Hi internals, > > > > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > Very good work, thank you :) > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > > previously. One significant difference is that the new RFC limits the scope > > of initializing assignments. I think a key mistake of the previous RFC was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > About this paragraph in > https://wiki.php.net/rfc/readonly_properties_v2#rationale: > > " The addition of readonly properties neither precludes nor > discourages the addition of asymmetric property visibility." > > To me this sentence is the meaning of a readonly property, not an > immutable property, as in writeable once property, in constructor. > > This RFC is perfect given its goal but the property attribute "readonly". > > How would you define a property only readable from outside the scope > but writeable inside the scope of its class (->status f.e.)? > > This is PHP and we always have our ways. That being said, the keyword > "readonly" is really about what is defined as "asymmetric property > visibility" in many other languages. Rust uses annotation, .net via > either readonly and they use an init setter to make it immutable in > v9 (public DateTime RecordedAt { get; init; }), other like java or > scala relies on setting a property getters only. > > Given this, I wonder if it would not be easier to have actual per > property getter/setter as an addition or replacement to the current > (kind of horrible) get/set($name, $value). This would all cases in > one shot, a more complicated shot but much more powerful. For the > record, I am totally not a fan of the 'manual" get/setMyProperty ;) > > In any case, I would at least put a second thought on the name "readonly". > > ps: sorry if this question has been discussed in previous RFCs, > pointers appreciated as I did not find it :) > > Best, > -- > Pierre
Pierre and Mike: "Asymmetric visibility" as we keep referring to it would mean the "implicit accessors only" version of this: https://wiki.php.net/rfc/property_accessors That is, it would let you define public/private/protected for get and set operations on a property separately from each other. There are three key differences between readonly and asymmetric visibility as described there: * Asymmetric visibility would allow a property to be reassigned multiple times from within a class, readonly would allow writing to it only once when it's uninitialized. Whether one of those is too-tight or too-loose is a matter of opinion and context. * Asymmetric visibility is deliberately a syntax that is forward-compatible with explicit accessor methods in the future. readonly would be a separate, independent feature/syntax. * Asymmetric visibility, IMO, is a stand-alone useful feature. readonly would be most useful if combined with a separate clone-with operator (discussed elsewhere). Whether readonly is useful enough on its own without that to justify its passage without a clone-with RFC also under discussion is an open question, and largely what we've been debating. Both approaches would eliminate *most* uses of __get/__set, which I think everyone agrees is a win. (Nikita, I hope I represented that accurately.) --Larry Garfield

Pierre Joye

5 years ago
Good morning Larry, Thank you. Very good summary, maybe worth adding to the RFC :) On Wed, Jun 9, 2021 at 11:52 PM Larry Garfield <larry@garfieldtech.com> wrote:
> readonly would be a separate, independent feature/syntax.
Yes, my thought is about self explanatory syntax matching common usage of the same syntax/keyword with other languages. In this case, readonly, it does not :) Absolutely not a big deal (one will google that more ;), however I do like some "consistency" across languages as we almost all rely on many and get used to syntax for very standard operations like this. Best,
-- Pierre @pierrejoye

Nikita Popov

5 years ago
On Thu, Jun 10, 2021 at 3:21 AM Pierre Joye <pierre.php@gmail.com> wrote:
> Good morning Larry, > > Thank you. Very good summary, maybe worth adding to the RFC :) > > On Wed, Jun 9, 2021 at 11:52 PM Larry Garfield <larry@garfieldtech.com> > wrote: > > > readonly would be a separate, independent feature/syntax. > > Yes, my thought is about self explanatory syntax matching common usage > of the same syntax/keyword with other languages. In this case, > readonly, it does not :) > > Absolutely not a big deal (one will google that more ;), however I do > like some "consistency" across languages as we almost all rely on many > and get used to syntax for very standard operations like this. >
The usage of "readonly" as proposed follows the commonly accepted interpretation in other languages (modulo details). Readonly properties refer to properties that only allow initializing assignments, though what exactly that means is language dependent (e.g. it can take the form of "only assignments inside the constructor"). Both C# and TypeScript use "readonly" in this manner. I'm not sure where you got the strange idea that "readonly" can refer to asymmetric visibility. The two syntactic approaches to asymmetric visibility that I'm aware of are "{ get; private set; }" in C# and "public private(set)" in Swift. Neither use the "readonly" terminology for this purpose. Using "readonly" to describe asymmetric visibility is semantically incorrect, as such properties are not, in fact, read-only. Regards, Nikita

Pierre Joye

5 years ago
On Thu, Jun 10, 2021, 2:32 PM Nikita Popov <nikita.ppv@gmail.com> wrote: I'm not sure where you got the strange idea that "readonly" can refer to
> asymmetric visibility. The two syntactic approaches to asymmetric > visibility that I'm aware of are "{ get; private set; }" in C# and "public > private(set)" in Swift. Neither use the "readonly" terminology for this > purpose. Using "readonly" to describe asymmetric visibility is semantically > incorrect, as such properties are not, in fact, read-only. >
I listed my refs in the initial reply :) And some like .net updates their syntax for init only write. I suppose I see it more as "from the outside of the class" and my personal usages are like that too. In any case, good rfc, naming perception is a details, important but a details for first time users.

Mike Schinkel

5 years ago
Hi Larry, Thanks for the response.
> On Jun 9, 2021, at 12:51 PM, Larry Garfield <larry@garfieldtech.com> wrote: > > Pierre and Mike: > > "Asymmetric visibility" as we keep referring to it would mean the "implicit accessors only" version of this: https://wiki.php.net/rfc/property_accessors > > That is, it would let you define public/private/protected for get and set operations on a property separately from each other. > > There are three key differences between readonly and asymmetric visibility as described there: > > * Asymmetric visibility would allow a property to be reassigned multiple times from within a class, readonly would allow writing to it only once when it's uninitialized. Whether one of those is too-tight or too-loose is a matter of opinion and context.
When I read your statement I first thought you were wrong, but then I re-read Nikitia's RFC[1] and realized that RFC stated the properties of an object assigned to a readonly property *could* be updated, but *not* the readonly property itself. I wonder if I was the only one who read that mistakenly? Anyway, have either you or Nikita considered making a distinction between 'public readonly' and 'private readonly' such that one could disallow any changes after initialization and the other could allow changes but only within the class? -Mike [1] https://wiki.php.net/rfc/readonly_properties_v2#proposal

Pierre Joye

5 years ago
On Thu, Jun 10, 2021 at 11:08 AM Mike Schinkel <mike@newclarity.net> wrote:
> > Hi Larry, > > Thanks for the response. > > > On Jun 9, 2021, at 12:51 PM, Larry Garfield <larry@garfieldtech.com>
wrote:
> > > > Pierre and Mike: > > > > "Asymmetric visibility" as we keep referring to it would mean the
"implicit accessors only" version of this: https://wiki.php.net/rfc/property_accessors
> > > > That is, it would let you define public/private/protected for get and
set operations on a property separately from each other.
> > > > There are three key differences between readonly and asymmetric
visibility as described there:
> > > > * Asymmetric visibility would allow a property to be reassigned
multiple times from within a class, readonly would allow writing to it only once when it's uninitialized. Whether one of those is too-tight or too-loose is a matter of opinion and context.
> > When I read your statement I first thought you were wrong, but then I
re-read Nikitia's RFC[1] and realized that RFC stated the properties of an object assigned to a readonly property *could* be updated, but *not* the readonly property itself. That's not related but an assignment by "reference" consequence. I think :)
> > I wonder if I was the only one who read that mistakenly? > > Anyway, have either you or Nikita considered making a distinction between
'public readonly' and 'private readonly' such that one could disallow any changes after initialization and the other could allow changes but only within the class? Better formulated than I tried :) I would like to have the readonly keyword to make a property readonly outside the class but read/write inside the class, together with visibility it should cover pretty much all cases (parent class included). Init only could be a separate keyword (allows init only, inside the class or outside the class being defined by the property visibility attributes, protected, private or public). I think users, and myself first when I read it, will have a hard time translating "asymmetric...." part of this whole thing (as in, if one has to go to wikipedia to understand what something does instead of what it looks like ;) ). best,
-- Pierre @pierrejoye

Nikita Popov

5 years ago
On Fri, Jun 4, 2021 at 5:19 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > I'd like to open the discussion on readonly properties: > https://wiki.php.net/rfc/readonly_properties_v2 > > This proposal is similar to the > https://wiki.php.net/rfc/write_once_properties RFC that has been declined > previously. One significant difference is that the new RFC limits the scope > of initializing assignments. I think a key mistake of the previous RFC was > the confusing "write-once" framing, which is both technically correct and > quite irrelevant. > > Please see the rationale section ( > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > proposal relates to other RFCs and alternatives. >
I plan to open voting on this RFC soon. I don't think there's anything technical left to address here, the discussion mostly comes down to a value judgement. I think everyone has made their position regarding that clear... Regards, Nikita

Nicolas Grekas

5 years ago
> > I'd like to open the discussion on readonly properties: > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > This proposal is similar to the > > https://wiki.php.net/rfc/write_once_properties RFC that has been > declined > > previously. One significant difference is that the new RFC limits the > scope > > of initializing assignments. I think a key mistake of the previous RFC > was > > the confusing "write-once" framing, which is both technically correct and > > quite irrelevant. > > > > Please see the rationale section ( > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > proposal relates to other RFCs and alternatives. > > > > I plan to open voting on this RFC soon. I don't think there's anything > technical left to address here, the discussion mostly comes down to a value > judgement. I think everyone has made their position regarding that clear... >
Actually, we talked off the list about a way to possibly make this work with __clone(): We could allow __clone to have one argument, the object being cloned. And when the signature declares this argument, then all readonly properties would be set as uninitialized on $this. A typical __clone function would look like this with readonly properties: function __clone(object $original) { $this->readonlyProp = clone $original->readonlyProp; } That would turn my vote into a +1 if that could be made to work! Cheers, Nicolas

Larry Garfield

5 years ago
On Mon, Jun 28, 2021, at 11:17 AM, Nicolas Grekas wrote:
> > > I'd like to open the discussion on readonly properties: > > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > > > This proposal is similar to the > > > https://wiki.php.net/rfc/write_once_properties RFC that has been > > declined > > > previously. One significant difference is that the new RFC limits the > > scope > > > of initializing assignments. I think a key mistake of the previous RFC > > was > > > the confusing "write-once" framing, which is both technically correct and > > > quite irrelevant. > > > > > > Please see the rationale section ( > > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how this > > > proposal relates to other RFCs and alternatives. > > > > > > > I plan to open voting on this RFC soon. I don't think there's anything > > technical left to address here, the discussion mostly comes down to a value > > judgement. I think everyone has made their position regarding that clear... > > > > Actually, we talked off the list about a way to possibly make this work > with __clone(): > > We could allow __clone to have one argument, the object being cloned. And > when the signature declares this argument, then all readonly properties > would be set as uninitialized on $this. > > A typical __clone function would look like this with readonly properties: > function __clone(object $original) > { > $this->readonlyProp = clone $original->readonlyProp; > } > > That would turn my vote into a +1 if that could be made to work!
That sounds like it would support deep cloning, but not with-er methods. There's no way to provide a changed value. It also would mean a lot of work on larger objects to transfer across all the properties. I don't really see what this would add. --Larry Garfield

Nicolas Grekas

5 years ago
Le lun. 28 juin 2021 à 18:22, Larry Garfield <larry@garfieldtech.com> a écrit :
> On Mon, Jun 28, 2021, at 11:17 AM, Nicolas Grekas wrote: > > > > I'd like to open the discussion on readonly properties: > > > > https://wiki.php.net/rfc/readonly_properties_v2 > > > > > > > > This proposal is similar to the > > > > https://wiki.php.net/rfc/write_once_properties RFC that has been > > > declined > > > > previously. One significant difference is that the new RFC limits the > > > scope > > > > of initializing assignments. I think a key mistake of the previous > RFC > > > was > > > > the confusing "write-once" framing, which is both technically > correct and > > > > quite irrelevant. > > > > > > > > Please see the rationale section ( > > > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how > this > > > > proposal relates to other RFCs and alternatives. > > > > > > > > > > I plan to open voting on this RFC soon. I don't think there's anything > > > technical left to address here, the discussion mostly comes down to a > value > > > judgement. I think everyone has made their position regarding that > clear... > > > > > > > Actually, we talked off the list about a way to possibly make this work > > with __clone(): > > > > We could allow __clone to have one argument, the object being cloned. And > > when the signature declares this argument, then all readonly properties > > would be set as uninitialized on $this. > > > > A typical __clone function would look like this with readonly properties: > > function __clone(object $original) > > { > > $this->readonlyProp = clone $original->readonlyProp; > > } > > > > That would turn my vote into a +1 if that could be made to work! > > That sounds like it would support deep cloning, but not with-er methods. > There's no way to provide a changed value. It also would mean a lot of > work on larger objects to transfer across all the properties. I don't > really see what this would add. >
Can you elaborate about the lack of support for withers? Having some work to do doesn't look like an issue to me, especially when there is no alternative to compare that too.

Nicolas Grekas

5 years ago
Le lun. 28 juin 2021 à 20:30, Nicolas Grekas <nicolas.grekas+php@gmail.com> a écrit :
> > > Le lun. 28 juin 2021 à 18:22, Larry Garfield <larry@garfieldtech.com> a > écrit : > >> On Mon, Jun 28, 2021, at 11:17 AM, Nicolas Grekas wrote: >> > > > I'd like to open the discussion on readonly properties: >> > > > https://wiki.php.net/rfc/readonly_properties_v2 >> > > > >> > > > This proposal is similar to the >> > > > https://wiki.php.net/rfc/write_once_properties RFC that has been >> > > declined >> > > > previously. One significant difference is that the new RFC limits >> the >> > > scope >> > > > of initializing assignments. I think a key mistake of the previous >> RFC >> > > was >> > > > the confusing "write-once" framing, which is both technically >> correct and >> > > > quite irrelevant. >> > > > >> > > > Please see the rationale section ( >> > > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for how >> this >> > > > proposal relates to other RFCs and alternatives. >> > > > >> > > >> > > I plan to open voting on this RFC soon. I don't think there's anything >> > > technical left to address here, the discussion mostly comes down to a >> value >> > > judgement. I think everyone has made their position regarding that >> clear... >> > > >> > >> > Actually, we talked off the list about a way to possibly make this work >> > with __clone(): >> > >> > We could allow __clone to have one argument, the object being cloned. >> And >> > when the signature declares this argument, then all readonly properties >> > would be set as uninitialized on $this. >> > >> > A typical __clone function would look like this with readonly >> properties: >> > function __clone(object $original) >> > { >> > $this->readonlyProp = clone $original->readonlyProp; >> > } >> > >> > That would turn my vote into a +1 if that could be made to work! >> >> That sounds like it would support deep cloning, but not with-er methods. >> There's no way to provide a changed value. It also would mean a lot of >> work on larger objects to transfer across all the properties. I don't >> really see what this would add. >> > > Can you elaborate about the lack of support for withers? Having some work > to do doesn't look like an issue to me, especially when there is no > alternative to compare that too. >
I sent that too fast, I agree about withers... :) I'm looking for a way to +1 that RFC... Any other idea?

Larry Garfield

5 years ago
On Mon, Jun 28, 2021, at 1:36 PM, Nicolas Grekas wrote:
> >> > Actually, we talked off the list about a way to possibly make this work > >> > with __clone(): > >> > > >> > We could allow __clone to have one argument, the object being cloned. > >> And > >> > when the signature declares this argument, then all readonly properties > >> > would be set as uninitialized on $this. > >> > > >> > A typical __clone function would look like this with readonly > >> properties: > >> > function __clone(object $original) > >> > { > >> > $this->readonlyProp = clone $original->readonlyProp; > >> > } > >> > > >> > That would turn my vote into a +1 if that could be made to work! > >> > >> That sounds like it would support deep cloning, but not with-er methods. > >> There's no way to provide a changed value. It also would mean a lot of > >> work on larger objects to transfer across all the properties. I don't > >> really see what this would add. > >> > > > > Can you elaborate about the lack of support for withers? Having some work > > to do doesn't look like an issue to me, especially when there is no > > alternative to compare that too. > > > > I sent that too fast, I agree about withers... :) > I'm looking for a way to +1 that RFC... > Any other idea?
IMO, the best solution to `readonly` and with-er methods is the discussed-but-not-proposed clone-with syntax. This RFC on its own: class Point { public function __construct( public readonly int $x, public readonly int $y, public readonly int $z, } public function withX(int $new): static { return new static( x: $new, y: $this->y, z: $this->z, ); } // And the same for withY() and withZ() } That does work, it's just ugly, and the amount of work needed scales exponentially with the number of properties. Same thing but adding clone-with: class Point { public function __construct( public readonly int $x, public readonly int $y, public readonly int $z, } public function withX(int $new): static { return clone($this) with {x: $new}; } // And the same for withY() and withZ() } The extra work needed here scales linearly with the number of properties. Clone-with isn't even a full proposal yet so definitely won't make it into 8.1. But, it's a standalone feature that could be added on its own later and complement `readonly`. (In case anyone hadn't noticed, I am a big fan of "features for free", which you get by designing smaller features to complement each other and become more than the sum of their parts.) The alternative is asymmetric visibility, which would allow for a withX() method like this: public function withX(int $new) { $that = clone($this); $that->x = $new; return $that; } Which is basically what withX() methods all do now already, so no change from status quo. It's more code per-property than clone-with, but still scales linearly. Nikita has made it clear he wants to proceed with readonly for now, though, and consider whether to also add asymmetric visibility in the Future(tm). I don't really see an alternative "easy fix" for readonly and with-er methods. I'll probably be voting +1 on readonly myself as is, even if it does make with-ers harder, because it would at least make the case that either clone-with or asymmetric visibility (or both, ideally) will be a necessity in 8.2. --Larry Garfield

Nicolas Grekas

5 years ago
> > Le lun. 28 juin 2021 à 18:22, Larry Garfield <larry@garfieldtech.com> a >> écrit : >> >>> On Mon, Jun 28, 2021, at 11:17 AM, Nicolas Grekas wrote: >>> > > > I'd like to open the discussion on readonly properties: >>> > > > https://wiki.php.net/rfc/readonly_properties_v2 >>> > > > >>> > > > This proposal is similar to the >>> > > > https://wiki.php.net/rfc/write_once_properties RFC that has been >>> > > declined >>> > > > previously. One significant difference is that the new RFC limits >>> the >>> > > scope >>> > > > of initializing assignments. I think a key mistake of the previous >>> RFC >>> > > was >>> > > > the confusing "write-once" framing, which is both technically >>> correct and >>> > > > quite irrelevant. >>> > > > >>> > > > Please see the rationale section ( >>> > > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for >>> how this >>> > > > proposal relates to other RFCs and alternatives. >>> > > > >>> > > >>> > > I plan to open voting on this RFC soon. I don't think there's >>> anything >>> > > technical left to address here, the discussion mostly comes down to >>> a value >>> > > judgement. I think everyone has made their position regarding that >>> clear... >>> > > >>> > >>> > Actually, we talked off the list about a way to possibly make this work >>> > with __clone(): >>> > >>> > We could allow __clone to have one argument, the object being cloned. >>> And >>> > when the signature declares this argument, then all readonly properties >>> > would be set as uninitialized on $this. >>> > >>> > A typical __clone function would look like this with readonly >>> properties: >>> > function __clone(object $original) >>> > { >>> > $this->readonlyProp = clone $original->readonlyProp; >>> > } >>> > >>> > That would turn my vote into a +1 if that could be made to work! >>> >>> That sounds like it would support deep cloning, but not with-er >>> methods. There's no way to provide a changed value. It also would mean a >>> lot of work on larger objects to transfer across all the properties. I >>> don't really see what this would add. >>> >> >> Can you elaborate about the lack of support for withers? Having some work >> to do doesn't look like an issue to me, especially when there is no >> alternative to compare that too. >> > > I sent that too fast, I agree about withers... :) > I'm looking for a way to +1 that RFC... > Any other idea? >
And I was too fast agreeing that my proposal to pass the original object as argument was incompatible with withers. I also think that not being compatible with deep cloning is a major issue. Past trivial cases, the use cases I can think of where I would use readonly require deep cloning. See eg the Symfony Request object where query params, headers, and a few others expose state as public objects. Here is some code that just works with them: class C { public readonly string $foo; public readonly string $bar; private $skipWhenCloning; public function withFoo($foo) { $this->skipWhenCloning = 'foo'; $clone = clone $this; $clone->foo = $foo; } public function withBar($bar) { $this->skipWhenCloning = 'bar'; $clone = clone $this; $clone->bar = $bar; } public function __clone(self $original) { foreach ($this as $k => $v) { if ($k !== $this->skipWhenCloning) { $this->$k = $original->$k; } } $this->skipWhenCloning = $original->skipWhenCloning = null; } } You might not like the boilerplate, but that just works. Can this be considered Nikita?

Nikita Popov

5 years ago
On Tue, Jun 29, 2021 at 9:14 AM Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> Le lun. 28 juin 2021 à 18:22, Larry Garfield <larry@garfieldtech.com> a >>> écrit : >>> >>>> On Mon, Jun 28, 2021, at 11:17 AM, Nicolas Grekas wrote: >>>> > > > I'd like to open the discussion on readonly properties: >>>> > > > https://wiki.php.net/rfc/readonly_properties_v2 >>>> > > > >>>> > > > This proposal is similar to the >>>> > > > https://wiki.php.net/rfc/write_once_properties RFC that has been >>>> > > declined >>>> > > > previously. One significant difference is that the new RFC limits >>>> the >>>> > > scope >>>> > > > of initializing assignments. I think a key mistake of the >>>> previous RFC >>>> > > was >>>> > > > the confusing "write-once" framing, which is both technically >>>> correct and >>>> > > > quite irrelevant. >>>> > > > >>>> > > > Please see the rationale section ( >>>> > > > https://wiki.php.net/rfc/readonly_properties_v2#rationale) for >>>> how this >>>> > > > proposal relates to other RFCs and alternatives. >>>> > > > >>>> > > >>>> > > I plan to open voting on this RFC soon. I don't think there's >>>> anything >>>> > > technical left to address here, the discussion mostly comes down to >>>> a value >>>> > > judgement. I think everyone has made their position regarding that >>>> clear... >>>> > > >>>> > >>>> > Actually, we talked off the list about a way to possibly make this >>>> work >>>> > with __clone(): >>>> > >>>> > We could allow __clone to have one argument, the object being cloned. >>>> And >>>> > when the signature declares this argument, then all readonly >>>> properties >>>> > would be set as uninitialized on $this. >>>> > >>>> > A typical __clone function would look like this with readonly >>>> properties: >>>> > function __clone(object $original) >>>> > { >>>> > $this->readonlyProp = clone $original->readonlyProp; >>>> > } >>>> > >>>> > That would turn my vote into a +1 if that could be made to work! >>>> >>>> That sounds like it would support deep cloning, but not with-er >>>> methods. There's no way to provide a changed value. It also would mean a >>>> lot of work on larger objects to transfer across all the properties. I >>>> don't really see what this would add. >>>> >>> >>> Can you elaborate about the lack of support for withers? Having some >>> work to do doesn't look like an issue to me, especially when there is no >>> alternative to compare that too. >>> >> >> I sent that too fast, I agree about withers... :) >> I'm looking for a way to +1 that RFC... >> Any other idea? >> > > And I was too fast agreeing that my proposal to pass the original object > as argument was incompatible with withers. > > I also think that not being compatible with deep cloning is a major issue. > Past trivial cases, the use cases I can think of where I would use readonly > require deep cloning. See eg the Symfony Request object where query params, > headers, and a few others expose state as public objects. > > Here is some code that just works with them: > class C > { > public readonly string $foo; > public readonly string $bar; > > private $skipWhenCloning; > > public function withFoo($foo) > { > $this->skipWhenCloning = 'foo'; > $clone = clone $this; > $clone->foo = $foo; > } > > public function withBar($bar) > { > $this->skipWhenCloning = 'bar'; > $clone = clone $this; > $clone->bar = $bar; > } > > public function __clone(self $original) > { > foreach ($this as $k => $v) { > if ($k !== $this->skipWhenCloning) { > $this->$k = $original->$k; > } > } > $this->skipWhenCloning = $original->skipWhenCloning = null; > } > } > > You might not like the boilerplate, but that just works. > > Can this be considered Nikita? >
Well, it's a nifty hack :) I don't think this is the solution we want to encourage though. It requires you pass extra information through a side-channel -- I think I'd rather not use readonly than write that code. Continuing along the same line, one could extend that to "clone with argument" and do something like this: public function __clone(self $original, array $replacements = []) { foreach ($original as $k => $v) { $this->$k = $replacements[$k] ?? $original->$k; } } and then do "clone $this(['bar' => $bar])". In any case, I don't want to include changes to cloning in this proposal -- the topic is related, but also orthogonal to readonly properties. Unfortunately, it will not be possible to get cloning changes into PHP 8.1 anymore, due to feature freeze. It's okay to vote against this if cloning is a deal breaker. In that case I'll probably either work on cloning before re-proposing this, or pivot to asymmetric visibility -- it's not my first preference, but it may be the more pragmatic choice. Cloning is definitely the weak point of this proposal. Regards, Nikita

Pierre

5 years ago
Le 29/06/2021 à 15:08, Nikita Popov a écrit :
> Well, it's a nifty hack :) I don't think this is the solution we want to > encourage though. It requires you pass extra information through a > side-channel -- I think I'd rather not use readonly than write that code. > > Continuing along the same line, one could extend that to "clone with > argument" and do something like this: > > public function __clone(self $original, array $replacements = []) { > foreach ($original as $k => $v) { > $this->$k = $replacements[$k] ?? $original->$k; > } > } > > and then do "clone $this(['bar' => $bar])". > > In any case, I don't want to include changes to cloning in this proposal -- > the topic is related, but also orthogonal to readonly properties. > Unfortunately, it will not be possible to get cloning changes into PHP 8.1 > anymore, due to feature freeze. > > It's okay to vote against this if cloning is a deal breaker. In that case > I'll probably either work on cloning before re-proposing this, or pivot to > asymmetric visibility -- it's not my first preference, but it may be the > more pragmatic choice. Cloning is definitely the weak point of this > proposal. > > Regards, > Nikita
Hello, I agree that the "clone with" feature, no matter how the syntax will end up being, will become vital as soon as readonly properties will be there for many people. Nevertheless, I also agree with you, both can be done at different times, and even if it will block _some_ usages of readonly properties, readonly properties as you propose remain a must-have (in my opinion) even without the "clone with". I'd be pleased to use them an write really immutable objects, even if for cloning I need to do new Foo($otherFoo->prop1, $otherFoo->prop2), that's still a huge win in my opinion. I can't vote, but I can't say nothing if people want to block this RFC for having the clone with at the same, whereas it could be in a RFC of its own, and readonly properties are still a huge feature on its own, even without "clone with" support. That's a very wrong reason to say no in my opinion: on the contrary, having readonly properties being accepted and implemented as-is will be a very persuasive argument in favor of the future "clone with" RFC that may emerge following this one.
-- Pierre

Larry Garfield

5 years ago
On Tue, Jun 29, 2021, at 8:08 AM, Nikita Popov wrote:
> > You might not like the boilerplate, but that just works. > > > > Can this be considered Nikita? > > > > Well, it's a nifty hack :) I don't think this is the solution we want to > encourage though. It requires you pass extra information through a > side-channel -- I think I'd rather not use readonly than write that code. > > Continuing along the same line, one could extend that to "clone with > argument" and do something like this: > > public function __clone(self $original, array $replacements = []) { > foreach ($original as $k => $v) { > $this->$k = $replacements[$k] ?? $original->$k; > } > } > > and then do "clone $this(['bar' => $bar])". > > In any case, I don't want to include changes to cloning in this proposal -- > the topic is related, but also orthogonal to readonly properties. > Unfortunately, it will not be possible to get cloning changes into PHP 8.1 > anymore, due to feature freeze. > > It's okay to vote against this if cloning is a deal breaker. In that case > I'll probably either work on cloning before re-proposing this, or pivot to > asymmetric visibility -- it's not my first preference, but it may be the > more pragmatic choice. Cloning is definitely the weak point of this > proposal. > > Regards, > Nikita
I already went through the clone-arguments mental exercise in my earlier analysis, and the code it produces is totally disgusting. :-) clone-with is a considerably better approach if you're starting from readonly. (It's also better if you start from asymmetric visibility, although that version needs a clone-help feature far less.) --Larry Garfield

Mike Schinkel

5 years ago
> On Jun 29, 2021, at 9:08 AM, Nikita Popov <nikita.ppv@gmail.com> wrote: > > In any case, I don't want to include changes to cloning in this proposal -- > the topic is related, but also orthogonal to readonly properties. > Unfortunately, it will not be possible to get cloning changes into PHP 8.1 > anymore, due to feature freeze. > > It's okay to vote against this if cloning is a deal breaker. In that case > I'll probably either work on cloning before re-proposing this,
I while back I made a suggestion for cloning which seemed to resolve the cloning issue in a way that no other proposals do. However, no one acknowledged it, so maybe few if any people saw it? https://externals.io/message/114729#114747 <https://externals.io/message/114729#114747> Anyway TL;DR, the approach is to allow specifying a closure on clone, binding $this to it, and then allow modifications to readonly properties while in that context and anything that it calls: $clone_obj = clone $obj fn() => { $this->foo = 'new value'; } Would this not be a viable solution for cloning, for this RFC, or the only you work on if this one fails to pass? -Mike