Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
--
Larry Garfield
larry@garfieldtech.com
Hi,
Am 05.08.22 um 19:08 schrieb Larry Garfield:
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
Great to hear that there is a new and simple proposals.
One note: In the example about references, I assume the class Baz must
extend from Foo.
Good luck with this proposal
Thomas
Hi,
Am 05.08.22 um 19:08 schrieb Larry Garfield:Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
Great to hear that there is a new and simple proposals.
One note: In the example about references, I assume the class Baz must
extend from Foo.
Yep, thanks, that's a typo on my part. Fixed now.
--Larry Garfield
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.
I have two comments:
-
For reflection purposes, having two separate methods feels like it will
be cumbersome; you'd need to check both to determine if you'd need to make
the reflection property accessible before changing the value. An
isPublicSet() would alleviate that, or potentially a getSetFlags() method
against which you could apply a bit mask. You'd need to add a constant for
public set I both cases. -
The number of items that appear to the left of a property is growing,
making understanding a declaration increasingly difficult, and your
discussion of future scope indicates that this is likely to get worse. I'm
wondering if this sort of behavior could be indicated via attributes
instead? Something like#[PropertySetBehavior(PROPERTY_SET_PRIVATE)]
.
Attributes have the benefit of being separate from the property
declaration, arguably more readable (one per line), and composable. This
might play into some of the future scope items as well.
Overall, though, love the design simplicity!
--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.I have two comments:
- For reflection purposes, having two separate methods feels like it will
be cumbersome; you'd need to check both to determine if you'd need to make
the reflection property accessible before changing the value. An
isPublicSet() would alleviate that, or potentially a getSetFlags() method
against which you could apply a bit mask. You'd need to add a constant for
public set I both cases.
Reflection is a mess, unfortunately. :-(
There's already separate isPrivate(), isProtected(), and isPublic() methods on ReflectionProperty, so this is consistent. Also, there's getModifiers() which returns a bitmask; these new flags would be included there. All these methods all do is check what is syntactically present, both the current ones and proposed ones.
There's definitely room in the Reflection API for a "couldItBeModifiedFromScope($scope)" type method (and similar) that aggregates together symmetric visibility, readonly, asymmetric visibility, etc. into nice booleans. However, that is notably more work than just adding more syntax flag methods. It would probably be better as its own RFC, though I'd support that.
If there's consensus that it has to be done here that could be done, but we'd prefer not as it feels like scope creep.
- The number of items that appear to the left of a property is growing,
making understanding a declaration increasingly difficult, and your
discussion of future scope indicates that this is likely to get worse. I'm
wondering if this sort of behavior could be indicated via attributes
instead? Something like#[PropertySetBehavior(PROPERTY_SET_PRIVATE)]
.
Attributes have the benefit of being separate from the property
declaration, arguably more readable (one per line), and composable. This
might play into some of the future scope items as well.
The list indeed groweth, but there's a couple of reasons it wouldn't be appropriate here to use an attribute.
- It would mean get-visibility is a keyword and set-visibility is an attribute. That's weird and inconsistent.
- There's a strong sense from many on the list that attributes should not be used for language behavior. Ilija agrees with that position, and I don't care enough to fight it one way or the other.
- An attribute would be ignored in older PHP versions. For most attributes that's OK, but in this case it would be a potential bug source. Specifically, it would mean
#[PrivateSet] \n public string $value
would be publicly settable on PHP 8.2, but not publicly settable in 8.3. That means the integrity of the object is compromised on older versions. A keyword would make that a syntax error, which in this case is preferable.
Overall, though, love the design simplicity!
Yay!
--Larry Garfield
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.I have two comments:
- For reflection purposes, having two separate methods feels like it will
be cumbersome; you'd need to check both to determine if you'd need to make
the reflection property accessible before changing the value. An
isPublicSet() would alleviate that, or potentially a getSetFlags() method
against which you could apply a bit mask. You'd need to add a constant for
public set I both cases.Reflection is a mess, unfortunately. :-(
There's already separate isPrivate(), isProtected(), and isPublic() methods on ReflectionProperty, so this is consistent. Also, there's getModifiers() which returns a bitmask; these new flags would be included there. All these methods all do is check what is syntactically present, both the current ones and proposed ones.
There's definitely room in the Reflection API for a "couldItBeModifiedFromScope($scope)" type method (and similar) that aggregates together symmetric visibility, readonly, asymmetric visibility, etc. into nice booleans. However, that is notably more work than just adding more syntax flag methods. It would probably be better as its own RFC, though I'd support that.
If there's consensus that it has to be done here that could be done, but we'd prefer not as it feels like scope creep.
- The number of items that appear to the left of a property is growing,
making understanding a declaration increasingly difficult, and your
discussion of future scope indicates that this is likely to get worse. I'm
wondering if this sort of behavior could be indicated via attributes
instead? Something like#[PropertySetBehavior(PROPERTY_SET_PRIVATE)]
.
Attributes have the benefit of being separate from the property
declaration, arguably more readable (one per line), and composable. This
might play into some of the future scope items as well.The list indeed groweth, but there's a couple of reasons it wouldn't be appropriate here to use an attribute.
- It would mean get-visibility is a keyword and set-visibility is an attribute. That's weird and inconsistent.
- There's a strong sense from many on the list that attributes should not be used for language behavior. Ilija agrees with that position, and I don't care enough to fight it one way or the other.
- An attribute would be ignored in older PHP versions. For most attributes that's OK, but in this case it would be a potential bug source. Specifically, it would mean
#[PrivateSet] \n public string $value
would be publicly settable on PHP 8.2, but not publicly settable in 8.3. That means the integrity of the object is compromised on older versions. A keyword would make that a syntax error, which in this case is preferable.Overall, though, love the design simplicity!
Yay!
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
Hello,
First of all, this is exciting!
Looking at the behavior for references, can we explore the edges a
bit? (I didn't see any tests that cover this functionality in the PR)
- Passing a function a reference to a private-set property is
allowed, such as tosort()
. - Returning a reference to a private-set property is allowed and usable.
- Trying to get a NEW reference to a private-set property outside of
the defined scope is not allowed.
So if I'm understanding correctly, references to private-set
properties can be used outside of the defined scope but they can't be
created outside of the defined scope? If so, should we fire off a
Notice when references are modified outside of the defined scope? I
can see that being annoying in certain contexts, such as sorting an
array, so maybe not? FWIW, this behavior doesn't apply to readonly
properties because attempting to pass a reference to the property
fails.
Hi Larry,
pt., 5 sie 2022, 19:09 użytkownik Larry Garfield larry@garfieldtech.com
napisał:
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.
Thanks for taking efforts on new RFC.
Why not C# style property accessors? It feels more natural for me while
Swift solution IMHO looks weird/odd composed with PHP class definition
syntax. The arguments in favor from the RFC look forcibly (couldn't find
better word).
Cheers,
Michał Marcin Brzuchalski
Hi Larry,
pt., 5 sie 2022, 19:09 użytkownik Larry Garfield larry@garfieldtech.com
napisał:Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.Thanks for taking efforts on new RFC.
Why not C# style property accessors? It feels more natural for me while
Swift solution IMHO looks weird/odd composed with PHP class definition
syntax. The arguments in favor from the RFC look forcibly (couldn't find
better word).Cheers,
Michał Marcin Brzuchalski
There's a couple of reasons. Ilija and I have spent quite a bit of time brainstorming through accessors, and there's still a lot to finalize there, and lots of bikeshed potential. It's a non-simple problem space. However, asymmetric visibility is a good stand-alone concept that can work without accessors, and has ample benefits of its own without accessors, and has far less bikeshed potential. (There's minor debates that might be had over syntax, but the behavior is fairly "it is or it isn't".)
So the primary reason is that this syntax allows us to have asymmetric visibility without having to think about accessors. We can then, at some point in the future, have a different discussion about accessors and not have to worry about asymmetric visibility in the process. The C# syntax by nature only works if they come together in the same RFC, or at least one RFC is built such that it locks in some decisions for the other RFC.
As an example, one thing we've been discussing is if accessors should be inline with the property (a la Swift and C#) or annotated methods (as in Javascript and Python). There's good arguments for both approaches, as well as good arguments against. But those would have extremely different implications for how we represent asymmetric visibility. So if we took the C# style for asymmetric visibility, that would realistically lock us into C# style accessors even before we've had the discussion about if we want that or not. That's not good.
(Please don't anyone try to debate which accessor approach to take here; the whole point is we want to introduce asymmetric visibility without having that debate yet. We'll be able to have it in the future, I'm sure. There's other knock-on effects as well that I'm skipping for now as not relevant.)
Keeping them separate also makes extending visibility rules in different directions (as noted in the Future Scope section) at some point in the future can also be done without bumping into accessors. Whether or not those directions are useful is a topic for another time, but it again allows us to discuss those (like a "once" operation or a "package" scope, etc.) without worrying about the impact on accessors.
Secondarily, the C# style means you have to look both before and after the property itself to know what its visibility rules are. That's increased cognitive load. This way, all of the various "flags" on a property remain in the same place, and presumably 99.999% of developers will put public private(set)
right next to each other like that. That makes the visibility readable all from one spot, and if all you're using is that and not accessors in the future, the impact on your existing reading skills is minimized. This one is admittedly a bit more subjective, but readability and cognitive load are certainly considerations to, er, consider.
--Larry Garfield
Hi Larry,
pt., 5 sie 2022, 19:09 użytkownik Larry Garfield <larry@garfieldtech.com
napisał:
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for
the
same.Thanks for taking efforts on new RFC.
Why not C# style property accessors? It feels more natural for me while
Swift solution IMHO looks weird/odd composed with PHP class definition
syntax. The arguments in favor from the RFC look forcibly (couldn't find
better word).Cheers,
Michał Marcin BrzuchalskiThere's a couple of reasons. Ilija and I have spent quite a bit of time
brainstorming through accessors, and there's still a lot to finalize there,
and lots of bikeshed potential. It's a non-simple problem space. However,
asymmetric visibility is a good stand-alone concept that can work without
accessors, and has ample benefits of its own without accessors, and has far
less bikeshed potential. (There's minor debates that might be had over
syntax, but the behavior is fairly "it is or it isn't".)So the primary reason is that this syntax allows us to have asymmetric
visibility without having to think about accessors. We can then, at some
point in the future, have a different discussion about accessors and not
have to worry about asymmetric visibility in the process. The C# syntax by
nature only works if they come together in the same RFC, or at least one
RFC is built such that it locks in some decisions for the other RFC.As an example, one thing we've been discussing is if accessors should be
inline with the property (a la Swift and C#) or annotated methods (as in
Javascript and Python). There's good arguments for both approaches, as
well as good arguments against. But those would have extremely different
implications for how we represent asymmetric visibility. So if we took the
C# style for asymmetric visibility, that would realistically lock us into
C# style accessors even before we've had the discussion about if we want
that or not. That's not good.(Please don't anyone try to debate which accessor approach to take here;
the whole point is we want to introduce asymmetric visibility without
having that debate yet. We'll be able to have it in the future, I'm sure.
There's other knock-on effects as well that I'm skipping for now as not
relevant.)Keeping them separate also makes extending visibility rules in different
directions (as noted in the Future Scope section) at some point in the
future can also be done without bumping into accessors. Whether or not
those directions are useful is a topic for another time, but it again
allows us to discuss those (like a "once" operation or a "package" scope,
etc.) without worrying about the impact on accessors.Secondarily, the C# style means you have to look both before and after the
property itself to know what its visibility rules are. That's increased
cognitive load. This way, all of the various "flags" on a property remain
in the same place, and presumably 99.999% of developers will putpublic private(set)
right next to each other like that. That makes the
visibility readable all from one spot, and if all you're using is that and
not accessors in the future, the impact on your existing reading skills is
minimized. This one is admittedly a bit more subjective, but readability
and cognitive load are certainly considerations to, er, consider.
Feedback:
-
The idea is cool
-
when I opened the RFC and looked at the syntax, I wasnt able to
intuitively (self documenting code) figure out what and why was going on. -
The syntax is heavily implicit as to what each keyword and syntax means,
so the general PHP developer would struggle or mis understand what is going
on. -
MWOP's point of there being a lot of keywords and prefixes at class
property declaration is not really cluttered (lots going on), is accurate.
Perhaps splitting it into an explicit Attribute might yield a better DX?
Ilina and Larry, all in all I know this is a WIP and you're looking for
early feedback, so this is my feedback, to be considered.
Looking forward to reviewing future revisions of this.
Thanks for the effort and time you've put in so far <3
--Larry Garfield
--
To unsubscribe, visit: https://www.php.net/unsub.php
when I opened the RFC and looked at the syntax, I wasnt able to
intuitively (self documenting code) figure out what and why was going on.The syntax is heavily implicit as to what each keyword and syntax means,
so the general PHP developer would struggle or mis understand what is going
on.
Can you expand on where you think the ambiguity / implicitness is? As I understand it, the RFC is proposing exactly three new combined access levels:
- "public private(set)"
- "public protected(set)"
- "protected private(set)"
Although aesthetically it will take a bit of getting used to, it seems to me pretty clear that the first means "mostly public, but private if you want to set it", and so on.
The only thing I can think of that could be described as "implicit" is that accessing a property by reference is considered a "set" operation, which I'm not sure how any implementation could avoid.
Regards,
--
Rowan Tommins
[IMSoP]
On Sun, Aug 7, 2022 at 12:34 PM Rowan Tommins rowan.collins@gmail.com
wrote:
Can you expand on where you think the ambiguity / implicitness is? As I
understand it, the RFC is proposing exactly three new combined access
levels:
- "public private(set)"
- "public protected(set)"
- "protected private(set)"
Although aesthetically it will take a bit of getting used to, it seems to
me pretty clear that the first means "mostly public, but private if you
want to set it", and so on.The only thing I can think of that could be described as "implicit" is
that accessing a property by reference is considered a "set" operation,
which I'm not sure how any implementation could avoid.
Personally for me it's the syntax. Reading "public private", "public
protected", or "protected private" reads really weird public private(set) static self $property
. In the end it makes sense if you know what it
means, otherwise it's probably confusing. I really like this RFC and I feel
like this might just be the way to go forward, but I have my doubts about
how many more keywords can be realistically added before it becomes a
problem.
On Sun, Aug 7, 2022 at 12:34 PM Rowan Tommins rowan.collins@gmail.com
wrote:Can you expand on where you think the ambiguity / implicitness is? As I
understand it, the RFC is proposing exactly three new combined access
levels:
- "public private(set)"
- "public protected(set)"
- "protected private(set)"
Although aesthetically it will take a bit of getting used to, it seems to
me pretty clear that the first means "mostly public, but private if you
want to set it", and so on.The only thing I can think of that could be described as "implicit" is
that accessing a property by reference is considered a "set" operation,
which I'm not sure how any implementation could avoid.Personally for me it's the syntax. Reading "public private", "public
protected", or "protected private" reads really weirdpublic private(set) static self $property
. In the end it makes sense if you know what it
means, otherwise it's probably confusing. I really like this RFC and I feel
like this might just be the way to go forward, but I have my doubts about
how many more keywords can be realistically added before it becomes a
problem.
What syntax would avoid having the visibility keyword repeated? if you want "public get, private set" behavior, I don't know how that could be done without having the words "public" and "private" both appear somewhere.
Also note, since using private(set) is mutually exclusive with readonly, that the resulting keyword list is about the same length as the existing "public readonly string $foo" that has cropped up frequently with PHP 8.1. So... yes it's another keyword, but it's not more keywords that can exist simultaneously. And moving the keyword to the right (a la C#) wouldn't make it any shorter; if anything it would be even more keystrokes.
Very close in size, same in complexity:
public readonly string $foo
public private(set) string $foo
--Larry Garfield
Hey Larry, hey all
On Sun, Aug 7, 2022 at 12:34 PM Rowan Tommins rowan.collins@gmail.com
wrote:Can you expand on where you think the ambiguity / implicitness is? As I
understand it, the RFC is proposing exactly three new combined access
levels:
- "public private(set)"
- "public protected(set)"
- "protected private(set)"
Although aesthetically it will take a bit of getting used to, it seems to
me pretty clear that the first means "mostly public, but private if you
want to set it", and so on.The only thing I can think of that could be described as "implicit" is
that accessing a property by reference is considered a "set" operation,
which I'm not sure how any implementation could avoid.Personally for me it's the syntax. Reading "public private", "public
protected", or "protected private" reads really weirdpublic private(set) static self $property
. In the end it makes sense if you know what it
means, otherwise it's probably confusing. I really like this RFC and I feel
like this might just be the way to go forward, but I have my doubts about
how many more keywords can be realistically added before it becomes a
problem.What syntax would avoid having the visibility keyword repeated? if you want "public get, private set" behavior, I don't know how that could be done without having the words "public" and "private" both appear somewhere.
Also note, since using private(set) is mutually exclusive with readonly, that the resulting keyword list is about the same length as the existing "public readonly string $foo" that has cropped up frequently with PHP 8.1. So... yes it's another keyword, but it's not more keywords that can exist simultaneously. And moving the keyword to the right (a la C#) wouldn't make it any shorter; if anything it would be even more keystrokes.
Very close in size, same in complexity:
public readonly string $foo
public private(set) string $foo
In my opinion those are actually two different things.
One is allowing a variable to be publicly readably and writeable. But it
can only be set once. Every subsequent call to setting a new value will
fail.
The other is allowing different scopes for reading and writing the
variable. The variable can be read publicly but can only be set from
within the current class-context. But setting it from the current class
context can happen multiple times.
So it should indeed be possible to combine both possibilities:
public(get) private(set) readonly string $foo;
My 0.02€
Cheers
Andreas
--
,,,
(o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| https://andreas.heigl.org |
+---------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
+---------------------------------------------------------------------+
Hi all,
In the discussion I sometimes see the terminology 'readonly' and 'writable' being used. This is confusing because when the property is an object that itself is mutable, there is nothing read-only about it.
The terminology in the RFC seems right to me, and overall it seems solid.
However, I'm not convinced this RFC is solving a real issue. I could not find any reasoning in the RFC, except that Swift has a very similar language feature.
Grtz, Casper
Hey Casper.
Hi all,
In the discussion I sometimes see the terminology 'readonly' and 'writable' being used. This is confusing because when the property is an object that itself is mutable, there is nothing read-only about it.
The terminology in the RFC seems right to me, and overall it seems solid.
However, I'm not convinced this RFC is solving a real issue. I could not find any reasoning in the RFC, except that Swift has a very similar language feature.
To me it solves the topic of making a property readable but not
writeable from the public while still allowing it to be written to
within the private or protected context.
So enforcing usage of a public setter-metbhod but not having to use a
getter.
Soemthing like this
final class Foo
{
public private(set) string $username;
public function changeUsernameTo(string $newUsername): self
{
if (! newUsernameIsUnique($newUsername) {
throw new RuntimeException('The username is not unique');
}
$this->username = $newUsername;
return $this;
}
}
readonly only solves that for immutable properties but there currently
is no way of solving that for mutable properties.
Just my 0.02€
Cheers
Andreas
--
,,,
(o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| https://andreas.heigl.org |
+---------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
+---------------------------------------------------------------------+
Heyo Andreas, Casper,
Hey Casper.
Hi all,
In the discussion I sometimes see the terminology 'readonly' and
'writable' being used. This is confusing because when the property is an
object that itself is mutable, there is nothing read-only about it.The terminology in the RFC seems right to me, and overall it seems solid.
However, I'm not convinced this RFC is solving a real issue. I could not
find any reasoning in the RFC, except that Swift has a very similar
language feature.To me it solves the topic of making a property readable but not
writeable from the public while still allowing it to be written to
within the private or protected context.So enforcing usage of a public setter-metbhod but not having to use a
getter.Soemthing like this
final class Foo
{
public private(set) string $username;public function changeUsernameTo(string $newUsername): self { if (! newUsernameIsUnique($newUsername) { throw new RuntimeException('The username is not unique'); } $this->username = $newUsername; return $this; }
}
readonly only solves that for immutable properties but there currently
is no way of solving that for mutable properties.
Similar question as Casper here: I use readonly
properties aggressively,
and I try to make the state as immutable as possible.
In the extremely rare cases where public get
and private set
are
needed, I rely on traditional getters and setters, which are becoming
extremely situational anyway, and still work perfectly fine.
If that doesn't work, then PHPStan/Psalm allow declaring @private
,
@internal
, @phpstan-internal
or similar mechanisms to restrict scope
access. qossmic/deptrac
also works wonders here, compared to PHP.
In fact, I'm writing so few getters and setters these days, that I don't
see why I'd need getter and setter semantics to creep into the language,
especially mutable ones, not even with the reflection improvements.
As for readonly
, the reason we sometimes cannot use readonly
is
because current clone
semantics can't work around readonly
rules
(discussed in the readonly
RFC): https://3v4l.org/og8bn
If we solved that, I think private(set)
would become even more
situational, if not completely unnecessary.
Another puzzling area in this RFC is reference support: let's take the
occasion to disallow references completely on asymmetric access, perhaps?
It would be nice to see references gone for good. No need to pollute the
engine for the edge case of ArrayAccess
and similar APIs :P
Marco Pivetta
Hey Marco.
Heyo Andreas, Casper,
Hey Casper.
Hi all,
In the discussion I sometimes see the terminology 'readonly' and
'writable' being used. This is confusing because when the property is an
object that itself is mutable, there is nothing read-only about it.The terminology in the RFC seems right to me, and overall it seems solid.
However, I'm not convinced this RFC is solving a real issue. I could not
find any reasoning in the RFC, except that Swift has a very similar
language feature.To me it solves the topic of making a property readable but not
writeable from the public while still allowing it to be written to
within the private or protected context.So enforcing usage of a public setter-metbhod but not having to use a
getter.Soemthing like this
final class Foo
{
public private(set) string $username;public function changeUsernameTo(string $newUsername): self { if (! newUsernameIsUnique($newUsername) { throw new RuntimeException('The username is not unique'); } $this->username = $newUsername; return $this; }
}
readonly only solves that for immutable properties but there currently
is no way of solving that for mutable properties.Similar question as Casper here: I use
readonly
properties aggressively,
and I try to make the state as immutable as possible.In the extremely rare cases where
public get
andprivate set
are
needed, I rely on traditional getters and setters, which are becoming
extremely situational anyway, and still work perfectly fine.
If that doesn't work, then PHPStan/Psalm allow declaring@private
,
@internal
,@phpstan-internal
or similar mechanisms to restrict scope
access.qossmic/deptrac
also works wonders here, compared to PHP. >
In fact, I'm writing so few getters and setters these days, that I don't
see why I'd need getter and setter semantics to creep into the language,
especially mutable ones, not even with the reflection improvements.
Your use case might not need them (though actually you are needing them,
you just don't use them as language feature but via the static-analysis
annotation)
But when discussing language features we should always keep ALL users of
the language in mind. And if a feature helps in providing ways to build
more stable code on the language level then we should think about that
from a point of view of all users.
And when the feature is already used via static-analysis then why not
implement that for use by everyone and not only those that have static
analysis in their CI toolchain.
Just my 0.02€
Cheers
Andreas
,,,
(o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| https://andreas.heigl.org |
+---------------------------------------------------------------------+
| https://hei.gl/appointmentwithandreas |
+---------------------------------------------------------------------+
Hey Andreas,
Your use case might not need them (though actually you are needing them,
you just don't use them as language feature but via the static-analysis
annotation)But when discussing language features we should always keep ALL users of
the language in mind. And if a feature helps in providing ways to build
more stable code on the language level then we should think about that
from a point of view of all users.And when the feature is already used via static-analysis then why not
implement that for use by everyone and not only those that have static
analysis in their CI toolchain.
Agreed, I was just highlighting that the need for this RFC:
- niche
- really niche
- I'm having a hard time expressing how extremely niche it is, so I
explained how I currently do it, perhaps once every 2 months :D
Marco Pivetta
Your use case might not need them (though actually you are needing them,
you just don't use them as language feature but via the static-analysis
annotation)But when discussing language features we should always keep ALL users of
the language in mind. And if a feature helps in providing ways to build
more stable code on the language level then we should think about that
from a point of view of all users.
I'm saying this reasoning (and the reasoning for the choice of syntax, as Rowan raised) should be part of the RFC. If this is the way the language is developing, it should be documented rather than being argued in a mailing list archive somewhere.
As for
readonly
, the reason we sometimes cannot usereadonly
is
because currentclone
semantics can't work aroundreadonly
rules
(discussed in thereadonly
RFC):https://3v4l.org/og8bn
If we solved that, I thinkprivate(set)
would become even more
situational, if not completely unnecessary.
I'm with Marco on this; the majority of use cases for asymetric
properties could be resolved if internally cloned objects could update
their readonly properties.
And I also find the syntax looks odd, and not like any other aspects of
PHP, not to mention the potential length of property definitions
--
Mark Baker
|. \ -3
|J/ PHP |
|| | __ |
|| |m| |m|
I LOVE PHP
Hi Larry,
niedz., 7 sie 2022 o 21:02 Larry Garfield larry@garfieldtech.com
napisał(a):
On Sun, Aug 7, 2022 at 12:34 PM Rowan Tommins rowan.collins@gmail.com
wrote:Can you expand on where you think the ambiguity / implicitness is? As I
understand it, the RFC is proposing exactly three new combined access
levels:
- "public private(set)"
- "public protected(set)"
- "protected private(set)"
Although aesthetically it will take a bit of getting used to, it seems
to
me pretty clear that the first means "mostly public, but private if you
want to set it", and so on.The only thing I can think of that could be described as "implicit" is
that accessing a property by reference is considered a "set" operation,
which I'm not sure how any implementation could avoid.Personally for me it's the syntax. Reading "public private", "public
protected", or "protected private" reads really weirdpublic private(set) static self $property
. In the end it makes sense if you know what it
means, otherwise it's probably confusing. I really like this RFC and I
feel
like this might just be the way to go forward, but I have my doubts about
how many more keywords can be realistically added before it becomes a
problem.What syntax would avoid having the visibility keyword repeated? if you
want "public get, private set" behavior, I don't know how that could be
done without having the words "public" and "private" both appear somewhere.Also note, since using private(set) is mutually exclusive with readonly,
that the resulting keyword list is about the same length as the existing
"public readonly string $foo" that has cropped up frequently with PHP 8.1.
So... yes it's another keyword, but it's not more keywords that can exist
simultaneously. And moving the keyword to the right (a la C#) wouldn't
make it any shorter; if anything it would be even more keystrokes.Very close in size, same in complexity:
public readonly string $foo
public private(set) string $foo
Since the radio request show already started wanted to push my 0.50€
Instead of thinking how to improve visibility on the left side, let's move
all the visibility modifiers to the right
but enclosed with parentheses, this way a future stays open for
improvements and introduction of accessors.
// instead of
class Foo {
public private(set) static int|string $id;
}
// let's do this
class Bar {
static int|string $id {
public;
private set;
};
}
A future improvement may add an accessor body on the right side of "set".
Cheers :D,
Michał Marcin Brzuchalski
Reading "public private", "public protected", or "protected private"
reads really weirdpublic private(set) static self $property
.
Interesting, it seems that you've unconsciously broken it up as "public
private" followed by "(set)", rather than "public" followed by
"private(set)". Perhaps it's because of the position of the parentheses,
which do feel awkward to me at first glance. Would it read more
naturally to you with different punctuation?
public (private set) static self $property;
Or:
(public; private set) static self $property;
Or:
public private-set static self $property;
I'm wondering if this sort of behavior could be indicated via attributes
instead? Something like#[PropertySetBehavior(PROPERTY_SET_PRIVATE)]
.
Attributes have the benefit of being separate from the property
declaration, arguably more readable (one per line), and composable.
Attributes are no more intrinsically "separate" or "one per line" than
keywords; the following would be perfectly valid with the RFC's proposed
syntax:
class Foo {
public private(set)
static int|string $id;
}
And the following would be valid with an attribute like you suggest:
class Foo {
#[PropertySetBehavior(PROPERTY_SET_PRIVATE)] public static
int|string $id;
}
Even a shorter attribute name would have the extra punctuation, and the
restriction that it comes before the normal visibility keyword:
class Foo {
#[PrivateSet] public static int|string $id;
}
Regards,
--
Rowan Tommins
[IMSoP]
Reading "public private", "public protected", or "protected private"
reads really weirdpublic private(set) static self $property
.Interesting, it seems that you've unconsciously broken it up as "public
private" followed by "(set)", rather than "public" followed by
"private(set)". Perhaps it's because of the position of the parentheses,
which do feel awkward to me at first glance. Would it read more
naturally to you with different punctuation?public (private set) static self $property;
Or:
(public; private set) static self $property;
Or:
public private-set static self $property;
I'm wondering if this sort of behavior could be indicated via attributes
instead? Something like#[PropertySetBehavior(PROPERTY_SET_PRIVATE)]
.
Attributes have the benefit of being separate from the property
declaration, arguably more readable (one per line), and composable.Attributes are no more intrinsically "separate" or "one per line" than
keywords; the following would be perfectly valid with the RFC's proposed
syntax:class Foo {
public private(set)
static int|string $id;
}And the following would be valid with an attribute like you suggest:
class Foo {
#[PropertySetBehavior(PROPERTY_SET_PRIVATE)] public static
int|string $id;
}Even a shorter attribute name would have the extra punctuation, and the
restriction that it comes before the normal visibility keyword:class Foo {
#[PrivateSet] public static int|string $id;
}Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
Just to throw in my ten cents:
I'm not sure if this is because I was recently reading about the fact that PHP doesn't have proper support for a comma operator, but I quite like the idea of providing them as a comma-separated list.
// Public for get and set
public int $number
// Public get, private set
public, private int $number
If a single visibility is provided, same old same old, but if two are provided, they are in the order of get, followed by set. While not being as verbose as providing (set) or other markings to differentiate them, it feels more natural, and it doesn't overcomplicate it.
Best Regards,
Ollie Read
Just to throw in my ten cents:
I'm not sure if this is because I was recently reading about the fact
that PHP doesn't have proper support for a comma operator, but I quite
like the idea of providing them as a comma-separated list.// Public for get and set
public int $number// Public get, private set
public, private int $numberIf a single visibility is provided, same old same old, but if two are
provided, they are in the order of get, followed by set. While not
being as verbose as providing (set) or other markings to differentiate
them, it feels more natural, and it doesn't overcomplicate it.
Best Regards,
Ollie Read
I'm not wild about implicit ordering as a syntax, for two main reasons:
-
It requires implicit knowledge on the part of the reader what's going on. Any kind of explicit syntax makes it clear "public, except for this operation". ("this operation" for now being just set, but then the set is right there and self-documenting.)
-
It makes extending it to other operations like "once" or "init" in the future impossible. (See Future Scope.) If we went with public, private string $foo, which implicitly meant "public(get), private(set)", and then in the future we add a "once" operation to mimic the behavior of readonly... what would that look like? With (set), it's self-evident how to extend the syntax. With it being fully implicit, I cannot think of a way to do so.
Something like public private:set (colon instead of parens) would work just as well, if the () are confusing somehow, but that doesn't feel like a common problem. And it would lose the parallelism with Swift.
--Larry Garfield
Something like public private:set (colon instead of parens) would work just as well, if the () are confusing somehow, but that doesn't feel like a common problem. And it would lose the parallelism with Swift.
I wonder if the reasoning for Swift's syntax choice is publicly
available, or if there's anyone with inside knowledge we could ask. It
would be good to know if there's a compelling argument the RFC could
mention, or if it was actually chosen for reasons that don't apply to PHP.
Regards,
--
Rowan Tommins
[IMSoP]
On Sun, Aug 7, 2022 at 10:38 PM Rowan Tommins rowan.collins@gmail.com
wrote:
Reading "public private", "public protected", or "protected private"
reads really weirdpublic private(set) static self $property
.Interesting, it seems that you've unconsciously broken it up as "public
private" followed by "(set)", rather than "public" followed by
"private(set)". Perhaps it's because of the position of the parentheses,
which do feel awkward to me at first glance. Would it read more
naturally to you with different punctuation?public (private set) static self $property;
Or:
(public; private set) static self $property;
Or:
public private-set static self $property;
The more I think about it, the more I agree with Marco's point of view on
this. I don't think there's a way to add this functionality without it
looking confusing one way or another. Once I get to use php8+, I'll
probably end up using readonly for most code. I think the feature itself is
nice for the scenario where you want to have private or protected
mutability, but even this is a scenario I would avoid myself. I'm afraid
that based on the future possibilities package(set)
will make it even
more confusing when allowing external modifications by specific classes,
which would make it even more complicated to find out who or what may or
may not modify a property from a reader perspective (like code reviews).
Le 5 août 2022 à 19:08, Larry Garfield larry@garfieldtech.com a écrit :
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
--
Larry Garfield
larry@garfieldtech.com
Hi,
I like the RFC, and in particular allowing property visibility to remain orthogonal to property accessors.
There are few missing details in the RFC (which is otherwise good):
-
Are static properties supported?
-
What about properties that are overridden/redeclared in a subclass? Given that extending the visibility is generally allowed:
class A {
protected int $x;
}
class B extends A {
public int $x; // ok
}
I expect that the following should be allowed as well:
class A {
public protected(set) int $x;
}
class B extends A {
public int $x; // ok
}
On the other hand, I expect that the two following cases should not be allowed, because it would break encapsulation of class A:
class A {
public private(set) int $x;
}
class B extends A {
public int $x; // error: cannot change a private property to public (or protected)
}
and:
class A {
public private(set) int $x;
}
class B extends A {
public private(set) int $x; // error: cannot make a private property in A as private in B
}
—Claude
On Fri, Aug 5, 2022 at 8:09 PM Larry Garfield larry@garfieldtech.com
wrote:
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi Larry, Ilija,
Review the proposal and it looks great.
I have a few subjects to point and maybe discuss further if you think so:
-
The first subject is visible even from the first phrase of the RFC that
uses both the terms "get and set operations" and "read and write
operations".
I'm wondering if you thought about using protected(write) or private(write). -
The term "asymmetric visibility" is not that accurate and I believe
"asymmetric access" or "asymmetric accessibility" would fit better.
As noted in the relation with __set(), a public private(set) property that
is attempted to be written from global context will not act like it's not
visible but that it's not accessible for writing. -
I assume the target is both static and non-static properties.
This should be mentioned in the RFC.
Regards,
Alex
On Fri, Aug 5, 2022 at 8:09 PM Larry Garfield larry@garfieldtech.com
wrote:Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi Larry, Ilija,
Review the proposal and it looks great.
I have a few subjects to point and maybe discuss further if you think so:
- The first subject is visible even from the first phrase of the RFC that
uses both the terms "get and set operations" and "read and write
operations".
I'm wondering if you thought about using protected(write) or private(write).
Not really. get/set are the terms used in every other language with such a feature, and the words are shorter. There's really no compelling reason to use a longer, less common keyword.
- I assume the target is both static and non-static properties.
This should be mentioned in the RFC.
That's the intent, although, now that you mention it, that part hasn't been implemented yet. :-) Ilija will add that to the patch and we'll update the RFC when it's been added. Thanks for the catch.
--Larry Garfield
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
For what it's worth, here's my initial thoughts after reading the RFC:
-
In "Permitted visibility, it explicitly states that the "set" scope
MUST be more restrictive than the "get" scope. However, no reason for
this is provided. Please make the argumentation to support this choice
explicit. -
In "Interaction with __set" and "Relationship with readonly" the RFC
states it wants to be consistent with the readonly behaviour, but that
is a different keyword. IMHO, these modifiers should work independently
of each other and are now improperly closely binded. If one wants
readonly behaviour, use thereadonly
keyword. -
I only see a hat-tip to the property accessors RFC regarding the
chosen syntax.
I'd like to see a more extensive discussion and consideration of various
possible syntaxes.
Some potential alternatives:
- "visibility(get) visibility(set) type $prop" - which makes it explicit
what each visibility applies to - "visibility(set: visibility) type $prop" - where it is clear that the
"main" visibility is the default and anything within the brackets a
deviation. This syntax would also more easily allow for expanding, like
"visibility(set: visibility, unset: visibility) type $prop"
I understand the desire for parallelism with Swift, but I don't think
that's a valid argument. The syntax should be right for PHP and if that
would happen to overlap/sync with a syntax used elsewhere: great, but
that's not an argument to only look at that syntax without a proper
discussion and due consideration for alternatives.
-
While I see no mention of "multi-property declarations", I presume
the visibility would apply to all ? I think it would be good to mention
this explicitly in the RFC as well.- By "multi-property declarations", I mean: `public string $type,
$value = 'me', $somethingElse;
- By "multi-property declarations", I mean: `public string $type,
-
Are there tokenizer implications ? How will
set
(and possibly
get
) be tokenized ? -
As this is something which can already be handled via magic methods,
is there sufficient reason to make the language/engine more complicated
to support this via a syntax ?
Overall, I mainly just see an idea in the RFC, but no justification for
the need for it, let alone for any of the choices made.
Smile,
Juliette
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
Conceptually, I believe this would add useful capabilities to PHP that are impossible to implement without:
a.) Likely-sizable performance penalties, and
b.) Unfortunate edge-cases; e.g. property_exists()
, get_object_vars()
, etc.
Still, here are my concerns with the RFC as it currently exists:
1.) The syntax of "<keyword>(<keyword>)" feels inconsistent with the rest of the language.
PHP has function(args...) and (<type>), both of which can be viewed as "a function of 'args'." However, "private(set)" is basically a constraint, not a "function of 'set'."
PHP also has function(args...):<type> which is a constraint so I propose we consider the following syntax instead:
<visibility>:<operation>
Thus all of these:
public:get
public:set
private:get
private:set
protected:get
protected:set
2.)The idiom "public private:set" is hard to reason about when reading, at least for me.
It also feels inconsistent in that it requires one type of visibility to be constrained, but not the other.
Speaking of "explicit" from one of Larry's latest replies, I propose we consider requiring asymmetric visibility explicitly declared, in all cases. Requiring explicit declaration would:
a.) Make asymmetric visibility easier to reason about, and
b.) Ensure consistency when asymmetric visibility is used.
Specifically I would propose we make these INVALID:
public protected:set <type> $<var>;
public private:set <type> $<var>;
protected private:set <type> $<var>;
But all of these following VALID:
These as-is for backward compatibility and an obvious default behavior:
public [<type>] $<var>;
protected [<type>] $<var>;
private [<type>] $<var>;
And these where behavior is explicit:
public:get protected:set <type> $<var>;
public:get private:set <type> $<var>;
protected:get private:set <type> $<var>;
As well as these:
public:set protected:get <type> $<var>;
public:set private:get <type> $<var>;
protected:set private:get <type> $<var>;
And of course these:
public:get public:set <type> $<var>;
private:get private:set <type> $<var>;
protected:get protected:set <type> $<var>;
3.) I have concerns about the proposed methods isProtectedSet() and isPrivateSet().
These names feels like we are asking if some thing "Set" is "Protected" or "Private" where no such "thing" exists in this context.
In other words it reads as if you are asking "Is it a protected Set?" or "Is it a private Set?" Such naming also does not relate to the Property itself which is what the prefix verb "is" is acting on.
I would propose instead we consider the following are these instead would be asking the ability to "Set" the Property is "Protected" or "Private" where The Property is again what the prefix verb "is" is acting on:
isSetProtected()
isSetPrivate()
isGetProtected(), and
isGetPrivate()
#jmtcw
-Mike
3.) I have concerns about the proposed methods isProtectedSet() and isPrivateSet().
These names feels like we are asking if some thing "Set" is "Protected" or "Private" where no such "thing" exists in this context.
In other words it reads as if you are asking "Is it a protected Set?" or "Is it a private Set?" Such naming also does not relate to the Property itself which is what the prefix verb "is" is acting on.
I would propose instead we consider the following are these instead would be asking the ability to "Set" the Property is "Protected" or "Private" where The Property is again what the prefix verb "is" is acting on:
isSetProtected()
isSetPrivate()
isGetProtected(), and
isGetPrivate()
I feel almost as if we shouldn't pollute ReflectionProperty with these additional methods but should instead have something like ReflectionAsymmetricProperty, which would in turn extend ReflectionProperty.
I guess it depends on how it's handled. If all property handling is updated so that "public int $number" is identical to "public:get public:set int $number", there's no issue, but if we treat those with separate visibilities to be additional, it probably makes sense to be somewhat separate.
While isGetProtected() is technically false for "public int $number", if it's not inferred, it's semantically incorrect. A good example of this is ReflectionMethod::getPrototype, which throws an exception if there's no prototype. Although this is somewhat bizarre, it is at least a thing we do.
Best Regards,
Ollie Read
Hi Ollie,
3.) I have concerns about the proposed methods isProtectedSet() and isPrivateSet().
These names feels like we are asking if some thing "Set" is "Protected" or "Private" where no such "thing" exists in this context.
In other words it reads as if you are asking "Is it a protected Set?" or "Is it a private Set?" Such naming also does not relate to the Property itself which is what the prefix verb "is" is acting on.
I would propose instead we consider the following are these instead would be asking the ability to "Set" the Property is "Protected" or "Private" where The Property is again what the prefix verb "is" is acting on:
isSetProtected()
isSetPrivate()
isGetProtected(), and
isGetPrivate()I feel almost as if we shouldn't pollute ReflectionProperty with these additional methods but should instead have something like ReflectionAsymmetricProperty, which would in turn extend ReflectionProperty.
On my 3rd point that you quoted my primary concern was that I felt the RFC solution of "is<Visbility>Set()" was hard to reason about but my own suggestion almost incidental. I felt if I voiced my concerns then it would be incumbent upon me to at least suggest an alternative.
OTOH, I'm not sure I would embrace yet another reflection class. I think that would make an already (IMO) too-complex reflection class hierarchy even more complex.
Maybe a better solution could be to add an optional $flags parameter to the existing is<Visbility>() methods, something like?
isPublic(ReflectionProperty::GETTABLE)
isProtected(ReflectionProperty::GETTABLE)
isPrivate(ReflectionProperty::GETTABLE)
isPublic(ReflectionProperty::SETTABLE)
isProtected(ReflectionProperty::SETTABLE)
isPrivate(ReflectionProperty::SETTABLE)
isPublic(ReflectionProperty::GETTABLE & ReflectionProperty::SETTABLE)
isProtected(ReflectionProperty::GETTABLE & ReflectionProperty::SETTABLE)
isPrivate(ReflectionProperty::GETTABLE & ReflectionProperty::SETTABLE)
#fwiw
-Mike
I guess it depends on how it's handled. If all property handling is updated so that "public int $number" is identical to "public:get public:set int $number", there's no issue, but if we treat those with separate visibilities to be additional, it probably makes sense to be somewhat separate.
While isGetProtected() is technically false for "public int $number", if it's not inferred, it's semantically incorrect. A good example of this is ReflectionMethod::getPrototype, which throws an exception if there's no prototype. Although this is somewhat bizarre, it is at least a thing we do.
Best Regards,
Ollie Read
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3: Asymmetric Visibility.
https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi Larry,
I like most of this, but I have one disagreement.
Can you expand a bit more on the reasoning for restricting the ability to combine __set() here?
The RFC states that it’s to keep consistency with readonly
, because __set on a readonly property that’s initialised throws an error - but isn’t that because of the nature of it being readonly, rather than because of the visibility rules? The error given is "Cannot modify readonly property” thrown from within the __set() method, not "Cannot access protected property” thrown from the outside calling context, when the __set() method is not implemented.
If I set a property to readonly I would expect it to not be settable after initialisation, regardless of visibility, whether it’s via direct access or via __set, etc. (i.e. what readonly does now)
But if I set a property to public private(set)
for example, I would expect that the __set method on that class (or in the hierarchy if using protected(set)) would be called when setting it from an inaccessible context, just as it would be called when setting a regular protected property.
It seems very unintuitive to me, that the first example here would work, but that the second would not work:
class Foo {
protected readonly int $bar;
public function __set(string $name, mixed $value): void {
$this->{$name} = $value;
}
}
$a = new Foo;
$a->bar = 1;
class Bar {
public protected(set) int $baz;
public function __set(string $name, mixed $value): void {
$this->{$name} = $value;
}
}
$b = new Bar;
$b->baz = 1;
Cheers
Stephen
The RFC states that it’s to keep consistency with
readonly
, because __set on a readonly property that’s initialised throws an error - but isn’t that because of the nature of it being readonly, rather than because of the visibility rules? The error given is "Cannot modify readonly property” thrown from within the __set() method, not "Cannot access protected property” thrown from the outside calling context, when the __set() method is not implemented.
If a property is "public readonly", the __set method is never called if
you try to reassign it: https://3v4l.org/8PioE The proposal is that the
same applies if it is "public private(set)": the assignment immediately
fails, rather than falling back to the __set method.
The logic, I think, is that a completely private property is
"invisible", so __set acts like it doesn't exist at all and runs
instead of the assignment; but a "public readonly" or "public
private(set)" property is "visible", so the assignment is attempted and
fails.
However, I am concerned that the rules around where __set and __get are
called are becoming increasingly complex with a lot of different
concepts interacting - "declared", "defined", "typed", "initialized",
"visible", "modifiable", etc.
As I mentioned in a previous discussion, I think we need to work towards
simplifying this - e.g. merge "typed" and "untyped" properties by making
"public $foo" equivalent to "public mixed $foo", which would remove the
distinction between "defined" and "initialized".
Regards,
--
Rowan Tommins
[IMSoP]
The RFC states that it’s to keep consistency with
readonly
, because __set on a readonly property that’s initialised throws an error - but isn’t that because of the nature of it being readonly, rather than because of the visibility rules? The error given is "Cannot modify readonly property” thrown from within the __set() method, not "Cannot access protected property” thrown from the outside calling context, when the __set() method is not implemented.If a property is "public readonly", the __set method is never called if you try to reassign it: https://3v4l.org/8PioE The proposal is that the same applies if it is "public private(set)": the assignment immediately fails, rather than falling back to the __set method.
The logic, I think, is that a completely private property is "invisible", so __set acts like it doesn't exist at all and runs instead of the assignment; but a "public readonly" or "public private(set)" property is "visible", so the assignment is attempted and fails.
However, I am concerned that the rules around where __set and __get are called are becoming increasingly complex with a lot of different concepts interacting - "declared", "defined", "typed", "initialized", "visible", "modifiable", etc.
As I mentioned in a previous discussion, I think we need to work towards simplifying this - e.g. merge "typed" and "untyped" properties by making "public $foo" equivalent to "public mixed $foo", which would remove the distinction between "defined" and "initialized".
Regards,
--
Rowan Tommins
[IMSoP]--
To unsubscribe, visit: https://www.php.net/unsub.php
Hi Rowan,
Thanks for trying to explain the logic here.
@Larry is Rowan’s assessment accurate?
Cheers
Stephen
Quite a feature to the language. Nice
I wonder if can be done using a different wording. If I can get an idea to
contribute I Will write again.
Keep it up!
On Fri, Aug 5, 2022 at 7:09 PM Larry Garfield larry@garfieldtech.com
wrote:
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.--
Larry Garfield
larry@garfieldtech.com--
To unsubscribe, visit: https://www.php.net/unsub.php
Just wanted to point out that I loved syntax proposals by Juliette and
Mike. Any of them would be better. Making a better syntax for PHP would be
better than consistency with Swift, IMO.
But I'm also inclined to agree with Marco Pivetta that this change is a lot
less exciting now that we have readonly
. In extremely rare cases where I
need to modify an attribute more than once, I just fallback to private +
getter and the nice thing about it is that it creates a consistent mindset
that if a property is being accessed via a getter, then the reader should
be advised that the property may change depending on WHEN it's accessed.
--
Marco Deleu
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.
Thank you Larry and Ilija for this RFC.
After reading it, I'm wondering about inheritance rules. What would be the
rules when redeclaring an existing asymmetric property in a child class?
The RFC should mention them I believe.
But I'm also wondering about the use case where asymmetry would be useful
in practice now that we have readonly?
I voted against readonly at the time because it didn't address cloning, and
also because it deprives end-users vs code authors IMHO (alike "final"). I
would have very much prefered asymmetric visibility back then. But now that
we are here, what are the use cases? If it's cloning only, then I would
very much prefer finding a way to fix it rather than providing two subtly
different ways to achieve the same thing.
Eg can't we allow __clone methods to bypass the readonly restrictions? The
engine already handles "guards" when using magic property accessors. Can't
we use something similar for clone?
And if yes, what would remain of the motivations for the asymmetric RFC?
Cheers,
Nicolas
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the
same.Thank you Larry and Ilija for this RFC.
After reading it, I'm wondering about inheritance rules. What would be the
rules when redeclaring an existing asymmetric property in a child class?
The RFC should mention them I believe.
Yeah, Ilija is working on that still, I believe. I believe the plan is, as with properties now, to allow widening access but not narrowing. static properties will also be supported, but that implementation is WIP.
But I'm also wondering about the use case where asymmetry would be useful
in practice now that we have readonly?
I voted against readonly at the time because it didn't address cloning, and
also because it deprives end-users vs code authors IMHO (alike "final"). I
would have very much prefered asymmetric visibility back then. But now that
we are here, what are the use cases? If it's cloning only, then I would
very much prefer finding a way to fix it rather than providing two subtly
different ways to achieve the same thing.Eg can't we allow __clone methods to bypass the readonly restrictions? The
engine already handles "guards" when using magic property accessors. Can't
we use something similar for clone?And if yes, what would remain of the motivations for the asymmetric RFC?
Cheers,
Nicolas
There's a couple of use cases I see off hand:
-
Cloneability, as you've noted.
-
Fixing inheritance with readonly. Right now, readonly is only settable from private scope, not protected. So child classes are SOL.
-
A property that is rebuild based on other properties. Eg, public private(set) $fullName, which is updated internally whenever $o->setFirstName() or $o->setLastName() are called. (Or with accessor hooks in the future, which is on the roadmap.)
There's likely others, too.
I would also have rather had AV in the first place rather than readonly, as readonly is now getting in the way. :-( But there are things readonly cannot reasonably do, and that's made worse by bad assumptions that static analysis tools have made about its usage.
We're working on a response to some other points made, but want to get on the same page with some more details first. Stay tuned for a follow up.
--Larry Garfield
Nicolas Grekas wrote:
But I'm also wondering about the use case where asymmetry would be
useful in practice now that we have readonly?
I voted against readonly at the time because it didn't address
cloning, and also because it deprives end-users vs code authors IMHO
(alike "final"). I would have very much prefered asymmetric
visibility back then. But now that we are here, what are the use cases?
Isn't it (obviously?) to have setters that ensure all invariants of the
properties are met? I'd still rather have Property Accessors as
described in https://wiki.php.net/rfc/property_accessors, but I think
AV is a step in the right direction.
As for readonly, I don't understand why that exists at all. Yes, it's
good that readonly properties can only be set from within the class,
but only once, so you get setters that throw when called a second time,
which I think is very, VERY unexpected behavior. Calling them
"readonly" is also somewhat misleading because of course the objects
assigned to those "readonly" properties can still be mutated, they can
just not be reassigned.
Nicolas Grekas wrote:
But I'm also wondering about the use case where asymmetry would be
useful in practice now that we have readonly?
I voted against readonly at the time because it didn't address
cloning, and also because it deprives end-users vs code authors IMHO
(alike "final"). I would have very much prefered asymmetric
visibility back then. But now that we are here, what are the use cases?Isn't it (obviously?) to have setters that ensure all invariants of the
properties are met? I'd still rather have Property Accessors as
described in https://wiki.php.net/rfc/property_accessors, but I think
AV is a step in the right direction.As for readonly, I don't understand why that exists at all.
For immutability.
Here is one reference on immutability offered up by the google gods:
https://hackernoon.com/5-benefits-of-immutable-objects-worth-considering-for-your-next-project-f98e7e85b6ac https://hackernoon.com/5-benefits-of-immutable-objects-worth-considering-for-your-next-project-f98e7e85b6ac
Immutability goes hand-in-hand with Functional Programming (FP.) While PHP is not a functional programming language, PHP developers can gain benefits from an FP style for which immutability is beneficial.
Also from the google gods, if you are unfamiliar with FP:
https://alvinalexander.com/scala/fp-book/benefits-of-functional-programming/ https://alvinalexander.com/scala/fp-book/benefits-of-functional-programming/
Hope this helps.
-Mike
P.S. Also, the Internals list's very own Larry Garfield is a huge advocate for FP in PHP, voilà:
https://leanpub.com/thinking-functionally-in-php
https://www.garfieldtech.com/taxonomy/term/95
Yes, it's
good that readonly properties can only be set from within the class,
but only once, so you get setters that throw when called a second time,
which I think is very, VERY unexpected behavior. Calling them
"readonly" is also somewhat misleading because of course the objects
assigned to those "readonly" properties can still be mutated, they can
just not be reassigned.--
To unsubscribe, visit: https://www.php.net/unsub.php
Ilija Tovilo and I are happy to present the first new RFC for PHP 8.3:
Asymmetric Visibility.https://wiki.php.net/rfc/asymmetric-visibility
Details are in the RFC, but it's largely a copy of Swift's support for the same.
Hi folks. Ilija and I have been discussing and experimenting extensively based on the feedback to the first draft. Here's our thoughts so far, some additional data, and a request for feedback. (These changes haven't been made to the RFC just yet, as there's outstanding questions for which we want feedback below.)
-
Rather than
readonly
being forbidden on asymmetric properties, it's better to redefine it as "write once, and if noset
visibility is defined, make it private." That would still allow forpublic protected(set) readonly string $blah
(to work around existing limitations inreadonly
), but also more or less preserve the current syntax in a logical way. -
Given that... we're not sure that it makes sense to have any visibility scope other than
get
/set
.once
becomes unnecessary with that understanding ofreadonly
, and we cannot think of any other examples that are not a stretch. So that axis of extensibility is of minimal importance. -
Since there has been some confusion, our goal is to implement property accessors in the future as a follow-on to this RFC. However, conceptually property "hooks" and asymmetric visibility are separate features. They also both have ample bikeshed potential. That's why we're proposing them as two separate RFCs. (Technically property hooks could also be done first, but this one is smaller.) That said, it's become obvious that we cannot avoid discussing property hooks at the same time since the syntax does overlap. So in the discussion below I will include property hooks, but deliberately gloss over parts that are not relevant right now. Please humor me and do the same. :-)
-
In concept, there's two main syntactic models for property hooks/accessors: Javascript/Python style (annotated methods) and Swift/C# style (method-esque code blocks on properties). We were trying to keep our options open and avoid a syntax that locks us into one or the other. However, we've concluded that the annotated methods style just wouldn't work at all in a language with explicit types and visibility controls. We've therefore made the executive decision that only the Swift/C# style is an option.
-
That leaves the basic question of "where to put the visibility," as Swift and C# put them in different places. (There are other differences not relevant for now.)
Swift:
class User {
public private(set) var first: String
public private(set) var last: String
public private(set) fullName: String {
get { ... }
set { ... }
}
}
C#:
class User {
public string first { get; private set; }
public string last { get; private set; }
public string fullName {
get { ... }
private set { ... }
}
}
The relevant difference is that in Swift, all visibility is on the left of the property while in C# it is split between the left and right.
The main argument for Swift-style: Keep everything in one place, and if there's no property hooks we don't even have to think about it.
The main argument for C#-style: It avoids repeating a keyword (eg, set
) if using both features.
It may be possible to omit the get
in the C# example above if it's just the default. Ilija is still working to figure that out.
References make everything worse
One other caveat to consider is (naturally) references. A get
hook cannot return by reference, as that would allow setting the value via its reference and bypassing a set
hook.
A side effect of that restriction is that arrays cannot be modified in place on properties with a get
hook. That is, assuming the $bar
property is an array that leverages the extra hooks above (by whatever syntax), the following is not possible:
$f = new Foo();
$f->bar[] = 'beep';
That would require returning $bar
by reference so that the []
operation could apply to it directly. However, that then bypasses any restrictions imposed by a set
hook (such as the values had to be integers). The following, however, would be legal:
$f = new Foo();
$b = $f->bar;
$b[] = 'beep';
$f->bar = $b;
That problem wouldn't happen if all we're changing is visibility, however. That means in the C# style, we would need to differentiate whether the block on the right is intended to disable references or not. Ilija has proposed raw
to indicate that. That would mean:
class Test
{
public $a { private set; }
public $b { private raw; }
public function inScope()
{
echo $this->a; // echo $this->getA();
$this->a = 'a'; // $this->setA('a');
echo $this->b; // echo $this->b;
$this->b = 'b'; // $this->b = 'b';
}
}
$test = new Test();
echo $test->a; // echo $test->getA();
$test->a = 'a'; // Error, set operation not accessible
echo $test->b; // echo $test->getB();
$test->b = 'b'; // Error, set operation not accessible
The take-away for the asymmetric visibility only case is that we would need to use raw
instead of set
, in order to avoid confusion later with accessor hooks and whether or not to disable references.
The Swift-style syntax, as it does not require any hook-like syntax to just control visibility, does not have this problem. There is no ambiguity to resolve, so no need for a raw
keyword.
Common use cases
While there are a myriad of possible ways to use both asymmetric visibility and property accessor hooks, we anticipate three usage patterns to predominate:
- Public read, private/protected write, but that's it.
- Public read, public write, but some extra validation on set.
- Public lazily-derived value, no write.
In particular, it seems to us (though we have no precise data to back it up) that it is unlikely that both asymmetric visibility and property hooks would be used at the same time, in the typical cases. That should inform the discussion about syntax.
Comparison
For the moment I'm just going to use Swift-style as-is. There's a discussion later to be had about minor variations on it, as suggested by a few people in the previous thread. Stand by.
Length comparison:
// Current code, for comparison
public readonly string $first;
// Swift-ish
public private(set) string $first;
// C#-ish, note "raw" because there's no hook
public string $first { get; private raw; }
Both options suggest possible shorthands:
// Current code, for comparison
public readonly string $first;
// Swift-ish
private(set) string $first;
// C#-ish, assuming it can be done
public string $first { private raw; }
If combining with readonly
, we get:
// Current code, for comparison
public readonly string $first;
// Swift-ish
public protected(set) readonly string $first;
protected(set) readonly string $first;
// C#-ish
public readonly string $first { get; private set; }
public readonly string $first { private raw; }
If we mix in for-reals accessor hooks, we get:
// Swift-ish
public private(set) string $first {
get { ... }
set { ... }
}
// C#-ish, note now we're using "set".
public string $first {
get { ... }
private set { ... }
}
If other hooks are added (Swift has beforeSet
and didSet
, and we intend to include those as well for PHP), they don't really make sense to have visibility modifiers on them. It's arguable if isset
or unset
(to mirror the existing magic methods) would want those. Does it make sense to have "public read, public set, but private unset"? Or is unset
just controlled by set
? Unclear.
Another complication is that, for forward compatibility, we would need a way to disable references on no-hook properties so that hooks could be added later without a change in reference behavior. Given the postfix syntax for hooks, we believe the most natural way to do that would be:
// Swift-ish
public string $foo {}
// C#-ish
public string $foo {}
// which is shorthand for:
public string $foo { public raw; }
Again, the C# model implies a bit more complexity and nuance.
Questions
Question 1: Which general model (Swift or C#) is preferable, and why?
(At the moment, Ilija and I disagree on which one is preferable. Hence, poll.)
If Swift
If we go with Swift-style, several people suggested alternative syntax options. This is mostly a bikeshed question, so I will just list them:
public private(set) string $first;
public private:set string $first;
public:private string $first;
public private string $first;
public(set: private) string $first;
Some of which have natural shorthands:
private(set) string $first;
private:set string $first;
:private string $first;
N/A
N/A
Question 2: IF we go with Swift-style, which of the above syntaxes would you prefer, and why?
Please answer both questions only via this poll so can collect actual data.
https://docs.google.com/forms/d/e/1FAIpQLSefq15VvGNIXSnQaMTl3RW451w0E8oesny8c4PLqmKl8HhQ-Q/viewform
Thank you all for your time and input.
--Larry Garfield and Ilija Tovilo
Hi
References make everything worse
[…]
That problem wouldn't happen if all we're changing is visibility, however. That means in the C# style, we would need to differentiate whether the block on the right is intended to disable references or not. Ilija has proposed
raw
to indicate that. That would mean:class Test { public $a { private set; } public $b { private raw; } public function inScope() { echo $this->a; // echo $this->getA(); $this->a = 'a'; // $this->setA('a'); echo $this->b; // echo $this->b; $this->b = 'b'; // $this->b = 'b'; } } $test = new Test(); echo $test->a; // echo $test->getA(); $test->a = 'a'; // Error, set operation not accessible echo $test->b; // echo $test->getB(); $test->b = 'b'; // Error, set operation not accessible
The take-away for the asymmetric visibility only case is that we would need to use
raw
instead ofset
, in order to avoid confusion later with accessor hooks and whether or not to disable references.The Swift-style syntax, as it does not require any hook-like syntax to just control visibility, does not have this problem. There is no ambiguity to resolve, so no need for a
raw
keyword.
I've read through that explanation three times, but don't understand why
the C#-style syntax would require to differentiate between set
and
raw
. Isn't the difference between "visibility only" and "accessor" not
clear [1] by having either '{' or ';' after the 'set'?
[1] Read: Clear to the engine, not talking about the human reader here.
Best regards
Tim Düsterhus
Hi
References make everything worse
[…]
That problem wouldn't happen if all we're changing is visibility, however. That means in the C# style, we would need to differentiate whether the block on the right is intended to disable references or not. Ilija has proposed
raw
to indicate that. That would mean:class Test { public $a { private set; } public $b { private raw; } public function inScope() { echo $this->a; // echo $this->getA(); $this->a = 'a'; // $this->setA('a'); echo $this->b; // echo $this->b; $this->b = 'b'; // $this->b = 'b'; } } $test = new Test(); echo $test->a; // echo $test->getA(); $test->a = 'a'; // Error, set operation not accessible echo $test->b; // echo $test->getB(); $test->b = 'b'; // Error, set operation not accessible
The take-away for the asymmetric visibility only case is that we would need to use
raw
instead ofset
, in order to avoid confusion later with accessor hooks and whether or not to disable references.The Swift-style syntax, as it does not require any hook-like syntax to just control visibility, does not have this problem. There is no ambiguity to resolve, so no need for a
raw
keyword.I've read through that explanation three times, but don't understand why
the C#-style syntax would require to differentiate betweenset
and
raw
. Isn't the difference between "visibility only" and "accessor" not
clear [1] by having either '{' or ';' after the 'set'?[1] Read: Clear to the engine, not talking about the human reader here.
Yeah, it's complicated, and Ilija had to explain it to me several times. :-)
The tricky part is that there's three different toggles involved here.
- Asymmetric visibility.
- Accessor hooks.
- Disabling references on properties.
Invoking 2 necessarily implies also doing 3, for reasons given above. However, we also want people to be able to enable 3 on its own, so that IF they enable accessor hooks later, it is transparent and doesn't imply breaking references unexpectedly. (Viz, you could have a value object full of public string $foo {}
type properties, which disables references, and then slip in a get/set hook later and no one notices.)
So we need a way to indicate "I'm using a-viz, and disabling references" as well as "I'm using a-viz, and not disabling references."
With the Swift style syntax, that's super straightforward.
public string $foo; // No a-viz, references work.
public string $foo {} // No a-viz, references disabled.
public private(set) string $foo; // a-viz, references work.
public private(set) string $foo {} // a-viz, references disabled.
And the {} makes a convenient marker because you'd be adding it in the future anyway if you add hooks, which would force disabling of references. So it's a natural flow from one option to the next.
With the C# style, a-viz means you're always putting braces on the right, so we cannot do that. Instead, you need
public string $foo; // No a-viz, references work.
public string $foo {} // No a-viz, references disabled.
public string $foo { private raw; } // a-viz, references work.
public string $foo { private set; } // a-viz, references disabled.
So then the toggle for disabling references is "there's a set or get keyword inside the braces", but we then need a different keyword for "I'm just doing a-viz, and I don't want to disable references." Hence, "raw".
(I'm assuming in the above that Ilija is able to make omitting the "get; set;" if there's no body for them. He's reasonably sure we can but hasn't confirmed it.)
Is that clearer?
--Larry Garfield