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
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_v2This 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
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_v2This 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,
NikitaHello,
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
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
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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
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
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_v2This 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
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_v2This 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,
NikitaHello,
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.
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,
NikitaHello,
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
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
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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']);
Regards,
Nikita
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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']);
Regards,
Nikita
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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.
-
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.
-
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.
-
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].)
-
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.
-
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.
-
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. :-)
-
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.
-
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.
-
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
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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,
NikitaThank you for the detailed analysis in the rationale section. I am, however, still skeptical of this approach, for a couple of reasons.
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.
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
On Sat, Jun 5, 2021 at 6:51 PM Larry Garfield larry@garfieldtech.com
wrote:
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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,
NikitaThank you for the detailed analysis in the rationale section. I am,
however, still skeptical of this approach, for a couple of reasons.
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.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?
- 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.
- 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.
- 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".
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. :-)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).
- 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.
- 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
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.
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.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?
- 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?)
- 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".
- 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. :-)
- 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.
- 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
On Mon, Jun 7, 2021 at 4:09 PM Larry Garfield larry@garfieldtech.com
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.
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.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?
- 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?)
- 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".
- 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. :-)
- 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
(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 resultsNote 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
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 resultsNote 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®exp=true&filter[lang][0]=C%23 https://beta.grep.app/search?current=2&q=get%5Cs%2A%5C%7B®exp=true&filter%5Blang%5D%5B0%5D=C%23
https://beta.grep.app/search?q=%5C%7B%5Cs%2Aset%5Cs%2A%5C%7B®exp=true&format=e&filter[lang][0]=C%23 https://beta.grep.app/search?q=%5C%7B%5Cs%2Aset%5Cs%2A%5C%7B®exp=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.
Hi Nikita,
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
Hi Nikita,
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2Very 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
Good morning Larry,
Thank you. Very good summary, maybe worth adding to the RFC :)
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
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
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.
Regards,
Nikita
Hi Larry,
Thanks for the response.
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
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_accessorsThat 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
Hi internals,
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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
Le lun. 28 juin 2021 à 18:22, Larry Garfield larry@garfieldtech.com a
écrit :
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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.
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 :I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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?
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
Le lun. 28 juin 2021 à 18:22, Larry Garfield larry@garfieldtech.com a
écrit :
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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?
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 :
I'd like to open the discussion on readonly properties:
https://wiki.php.net/rfc/readonly_properties_v2This 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
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
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
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