[PHP-DEV] [RFC] Asymmetric Visibility, v2

As promised, Ilija and I offer this revised version of asymmetric visibility.

https://wiki.php.net/rfc/asymmetric-visibility-v2

It's still essentially the same as last year's version, but with a few adjustments and changes:

* readonly properties are now supported in a logical fashion.
* We've brought back the abbreviated form, as public-read, something else set is the most common use case.
* The section on magic methods has been greatly simplified. The implementation itself hasn't changed, but the explanation is a lot less confusing now.
* We've explained how aviz interacts with hooks (they don't, really) and with interface properties (in the obvious way), which didn't exist at the time of the last draft.
* We've added a section with examples of how aviz is a concrete improvement, even in a world with readonly and hooks.
* We've added a section discussing why the prefix-style syntax was chosen.

*dons flame retardant suit*

--
  Larry Garfield
  larry@garfieldtech.com

Hi

On 5/29/24 21:53, Andreas Hennings wrote:

Is there a reason why we cannot just make it "public private string
$x" instead of "public private(set) string $x"?
We would define that the second visibility specifier is for write.

The current proposal with "public private(set)" is less ambiguous, and
it is immediately obvious that this is something new, and not just
somebody accidentally added two modifiers.
At the same time, it feels a bit alien and cluttered.

Other options could be something like "public:private" or "public-private".

The variant with the parentheses is the most future-proof one, should additional operations be added. It would allow for:

     public private(set,unset,frobnicate) string $foo;

Even if tools would not yet understand whatever 'frobnicate' does, they would know that this is an operation that may be performed on $foo and would be able to syntax highlight the visibility definition properly and they would also know how to autoformat it, without needing to add support for the new keyword.

Best regards
Tim Düsterhus

Hello Larry,
just a quick thought.
Is there a reason why we cannot just make it "public private string
$x" instead of "public private(set) string $x"?
We would define that the second visibility specifier is for write.

The current proposal with "public private(set)" is less ambiguous, and
it is immediately obvious that this is something new, and not just
somebody accidentally added two modifiers.
At the same time, it feels a bit alien and cluttered.

Other options could be something like "public:private" or "public-private".

A consequence of such options would be that you always need to specify
the read visibility along with the write visibility.
But this seems ok to me.

This is not a final opinion, just a thought.

-- Andreas

On Wed, 29 May 2024 at 21:17, Larry Garfield <larry@garfieldtech.com> wrote:

As promised, Ilija and I offer this revised version of asymmetric visibility.

PHP: rfc:asymmetric-visibility-v2

It's still essentially the same as last year's version, but with a few adjustments and changes:

* readonly properties are now supported in a logical fashion.
* We've brought back the abbreviated form, as public-read, something else set is the most common use case.
* The section on magic methods has been greatly simplified. The implementation itself hasn't changed, but the explanation is a lot less confusing now.
* We've explained how aviz interacts with hooks (they don't, really) and with interface properties (in the obvious way), which didn't exist at the time of the last draft.
* We've added a section with examples of how aviz is a concrete improvement, even in a world with readonly and hooks.
* We've added a section discussing why the prefix-style syntax was chosen.

*dons flame retardant suit*

--
  Larry Garfield
  larry@garfieldtech.com

On Wed, May 29, 2024, at 7:53 PM, Andreas Hennings wrote:

Hello Larry,
just a quick thought.
Is there a reason why we cannot just make it "public private string
$x" instead of "public private(set) string $x"?
We would define that the second visibility specifier is for write.

The current proposal with "public private(set)" is less ambiguous, and
it is immediately obvious that this is something new, and not just
somebody accidentally added two modifiers.
At the same time, it feels a bit alien and cluttered.

Other options could be something like "public:private" or "public-private".

A consequence of such options would be that you always need to specify
the read visibility along with the write visibility.
But this seems ok to me.

This is not a final opinion, just a thought.

-- Andreas

We went through a bunch of syntax variations last year, including "public private", "public:private", and "public private:set", plus a few others. In an RCV poll, public private(set) was the favorite. (See link at the end of the RFC.) It also allows for extension to other operations and scopes, and for the short-hand syntax. Many of the other options did not support those. Thus we stuck with the known syntax that had the most flexibility and most support.

By other operations, I mean, suppose we allow varying the visibility for obtaining a reference separate from a set (for some reason). It's obvious how that would look with the current syntax: public protected(ref) private(set). With "public private" or "public:private", it's really not clear how we'd even do that.

--Larry Garfield

Hi

On 5/29/24 21:15, Larry Garfield wrote:

* We've brought back the abbreviated form, as public-read, something else set is the most common use case.

The most common use case is that 'get' and 'set' are symmetric. Any divergence from that should stand out and I think that the hamming distance between

     protected string $foo;

and

     protected(set) string $foo;

is too small.

Other than that the proposal looks good to me. Ship it.

------

One note regarding the text. You already confirmed to me in private that:

     class Foo {
         private $dontTouchMe;
     }

     $backdoor = function ($key, $value) { $this->{$key} = $value; };

     $f = new Foo();
     $backdoor->call($f, 'dontTouchMe', 'butIDid');
     var_dump($f);

would work as expected with aviz. It would make sense to explicitly spell that out, just like it's explicitly spelled out that `ReflectionProperty::setValue()` works.

Best regards
Tim Düsterhus

On Wed, May 29, 2024, at 7:51 PM, Tim Düsterhus wrote:

Hi

On 5/29/24 21:15, Larry Garfield wrote:

* We've brought back the abbreviated form, as public-read, something else set is the most common use case.

The most common use case is that 'get' and 'set' are symmetric.

OK, fair, I meant in the most common case where you're using aviz at all, get is probably public.

That said, with both hooks and aviz, I can see data objects, for instance, becoming commonly public-get, private-set. It's already common to have them be public readonly, so this is just an extension of that.

Any
divergence from that should stand out and I think that the hamming
distance between

     protected string $foo;

and

     protected(set) string $foo;

is too small.

I can only respectfully disagree here. I think it's reasonably self-evident, made moreso by the (), which as Andreas noted looks weird when you're not used to it (but we should get used to fairly quickly). And the benefit of not having to type "public" on every property outweighs any initial confusion, much the same as readonly classes just reduces boilerplate.

One note regarding the text. You already confirmed to me in private that:

     class Foo {
         private $dontTouchMe;
     }

     $backdoor = function ($key, $value) { $this->{$key} = $value; };

     $f = new Foo();
     $backdoor->call($f, 'dontTouchMe', 'butIDid');
     var_dump($f);

would work as expected with aviz. It would make sense to explicitly
spell that out, just like it's explicitly spelled out that
`ReflectionProperty::setValue()` works.

Added a note to that effect in the Reflection section as well. Thanks.

--Larry Garfield

On Wed, May 29, 2024 at 10:18 PM Larry Garfield <larry@garfieldtech.com> wrote:

As promised, Ilija and I offer this revised version of asymmetric visibility.

https://wiki.php.net/rfc/asymmetric-visibility-v2

Hey Larry, Ilija,

I have one concern so far, and it’s related to the inheritance section.

If in a class I define the property as private,
I know that there is no way for external scope or extending classes scope to read or write to the property.
(of course, ignoring reading/writing using reflection or re-binded closures)

If an extending class defines the property with a wider visibility, protected or public, it will shadow the initial one and not change its visibility.

Now, if I define the property as public private(set) (or protected private(set)) with similar intentions,
to make sure that there is no way for external scope or extending classes scope to write to the property,
while allowing reading from external scope (or extending classes scope).

But the problem is that an extending class can define the property as public protected(set) (or protected protected(set)),
and that will easily allow the property that I wanted to make sure it is private for writing to be changed by an extending class to be protected.

The main suggestion I can think of, is to now allow widening the write visibility when it is private.

I mean, in general, to make sure it is not possible for other extending classes to access private parent class properties/methods we can use two mechanisms:

  • disallowing to change the visibility and making it an error
  • shadowing the parent property/method
    While shadowing works for symmetric visibility, it doesn’t really work for asymmetric visibility, so disallowing seems to be a good option here.

Also, maybe marking the properties as final could play a role here,
not allowing private visibility for write on properties to be widened or completely not allowing it to change/redefined.

Regards,
Alex

-----Original Message-----
From: Larry Garfield <larry@garfieldtech.com>
Sent: Wednesday, May 29, 2024 10:03 PM

On Wed, May 29, 2024, at 7:53 PM, Andreas Hennings wrote:
> Hello Larry,
> just a quick thought.
> Is there a reason why we cannot just make it "public private string
> $x" instead of "public private(set) string $x"?
> We would define that the second visibility specifier is for write.
>
> The current proposal with "public private(set)" is less ambiguous, and
> it is immediately obvious that this is something new, and not just
> somebody accidentally added two modifiers.
> At the same time, it feels a bit alien and cluttered.
>
> Other options could be something like "public:private" or "public-
private".
>
> A consequence of such options would be that you always need to specify
> the read visibility along with the write visibility.
> But this seems ok to me.
>
> This is not a final opinion, just a thought.

We went through a bunch of syntax variations last year, including "public
private", "public:private", and "public private:set", plus a few others.
In an RCV poll, public private(set) was the favorite. (See link at the end
of the RFC.) It also allows for extension to other operations and scopes,
and for the short-hand syntax. Many of the other options did not support
those. Thus we stuck with the known syntax that had the most flexibility
and most support.

Would it make sense to do another RCV poll now that hooks are accepted, after lengthy discussion over its syntax?

Personally, I would prefer the HookEmbeddedStyle form. I don't think the argument that the visibility would potentially be separated from the hook definition is very strong, as you would have to scan for the existence of the hook anyway. For me, asymmetric visibility and property hooks are mentally more related than the RFC technically defines them to be.

Furthermore, I see a lot of reasoning in favour of the prefix syntax in relation to limitations of other language constructs like property promotion. While I don't think it is fair to expect this RFC should fix those, to me it feels we are compounding 'errors'.

Thanks to both you and Ilija for all the hard work on these RFC's, it is much appreciated!

--
Vincent de Lau

On Thu, 30 May 2024, Alexandru Pătrănescu wrote:

On Wed, May 29, 2024 at 10:18 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> As promised, Ilija and I offer this revised version of asymmetric
> visibility.
>
> PHP: rfc:asymmetric-visibility-v2
>
>
Hey Larry, Ilija,

I have one concern so far, and it's related to the inheritance section.

If in a class I define the property as private,
I know that there is no way for external scope or extending classes scope
to read or write to the property.
(of course, ignoring reading/writing using reflection or re-binded closures)

If an extending class defines the property with a wider visibility,
protected or public, it will shadow the initial one and not change its
visibility.

private and protected differ here already, even without async
visibility:

A protected property does not create a new bag to store data in, which
does happen for a private property.

Now, if I define the property as public private(set) with similar
intentions, to make sure that there is no way for external scope or
extending classes scope to write to the property, while allowing
reading from external scope (or extending classes scope).

But the problem is that an extending class can define the property as
public protected(set), and that will easily allow the property that I
wanted to make sure it is private for writing to be changed by an
extending class to be protected.

public private(set) properties aren't really private, so you don't get
the shadowing, but you do have a point wrt to the expectation that an
inherited class can't easily override the private(set) part (with
protected(set) or public(set)).

Hopefully Ilija or Larry can explain :slight_smile:

cheers,
Derick

Le 30 mai 2024 à 17:07, Derick Rethans derick@php.net a écrit :

Now, if I define the property as public private(set) with similar
intentions, to make sure that there is no way for external scope or
extending classes scope to write to the property, while allowing
reading from external scope (or extending classes scope).

But the problem is that an extending class can define the property as
public protected(set), and that will easily allow the property that I
wanted to make sure it is private for writing to be changed by an
extending class to be protected.

public private(set) properties aren’t really private, so you don’t get
the shadowing, but you do have a point wrt to the expectation that an
inherited class can’t easily override the private(set) part (with
protected(set) or public(set)).

Note that the issue already exists today with readonly properties: those are basically private(set); but if you redeclare a non-private readonly property in a subclass, you can in fact initialise it from the subclass bypassing the initial private(set) restriction of the superclass: https://3v4l.org/9AV4r

If you want a property not to be overridable, end of story, you can mark it as final (the final marker for properties was added as part of the hooks RFC, but it works also with non-hooked properties).

—Claude

On Fri, May 31, 2024 at 10:30 AM Claude Pache <claude.pache@gmail.com> wrote:

Le 30 mai 2024 à 17:07, Derick Rethans <derick@php.net> a écrit :

Now, if I define the property as public private(set) with similar
intentions, to make sure that there is no way for external scope or
extending classes scope to write to the property, while allowing
reading from external scope (or extending classes scope).

But the problem is that an extending class can define the property as
public protected(set), and that will easily allow the property that I
wanted to make sure it is private for writing to be changed by an
extending class to be protected.

public private(set) properties aren’t really private, so you don’t get
the shadowing, but you do have a point wrt to the expectation that an
inherited class can’t easily override the private(set) part (with
protected(set) or public(set)).

Note that the issue already exists today with readonly properties: those are basically private(set); but if you redeclare a non-private readonly property in a subclass, you can in fact initialise it from the subclass bypassing the initial private(set) restriction of the superclass: https://3v4l.org/9AV4r

Yes, thank you; that’s a good point.
Seems like another issue with readonly, but then again, the private(set) part of readonly is not really explicitly designed, I guess.

If you want a property not to be overridable, end of story, you can mark it as final (the final marker for properties was added as part of the hooks RFC, but it works also with non-hooked properties).

Yes, it seems like a good enough option, and use “final public private(set)” to ensure only the current class will be able to set the value.
If we all agree, I think this should be documented in the RFC.

There is another small problem, I think, with “final” modifier not being allowed for constructor-promoted properties.
And maybe we can have this fixed in the current RFC if this ends up being “the correct” way to define public-read private-write properties.

Regards,
Alex

Le 30 mai 2024 à 12:16, Vincent de Lau vincent@delau.nl a écrit :

We went through a bunch of syntax variations last year, including “public
private”, “public:private”, and “public private:set”, plus a few others.
In an RCV poll, public private(set) was the favorite. (See link at the end
of the RFC.) It also allows for extension to other operations and scopes,
and for the short-hand syntax. Many of the other options did not support
those. Thus we stuck with the known syntax that had the most flexibility
and most support.

Would it make sense to do another RCV poll now that hooks are accepted, after lengthy discussion over its syntax?

At the time the poll was conducted, it was already known that a hooks RFC was in preparation, that could be compatible with either option, syntax-wise. Now, we have hooks that are compatible with both options, syntax-wise. I don’t think that would change the aesthetic preferences of people.

But now, we have indeed more information, namely detailed technical information on how the two features (hooks and aviz) interact effectively with references/arrays/readonly. At the time the poll was conducted, I was moderately in favour of the Swift-style syntax, mostly based on the general principle that things that are logically orthogonal should be implemented as orthogonal. If the same poll is done today, I will be strongly in favour of the Swift-style syntax, because I know more precisely how both features interact with arrays and readonly.

—Claude

On Fri, May 31, 2024, at 12:04 PM, Alexandru Pătrănescu wrote:

On Fri, May 31, 2024 at 10:30 AM Claude Pache <claude.pache@gmail.com> wrote:

Le 30 mai 2024 à 17:07, Derick Rethans <derick@php.net> a écrit :

Now, if I define the property as public private(set) with similar
intentions, to make sure that there is no way for external scope or
extending classes scope to write to the property, while allowing
reading from external scope (or extending classes scope).

But the problem is that an extending class can define the property as
public protected(set), and that will easily allow the property that I
wanted to make sure it is private for writing to be changed by an
extending class to be protected.

public private(set) properties aren't really private, so you don't get
the shadowing, but you do have a point wrt to the expectation that an
inherited class can't easily override the private(set) part (with
protected(set) or public(set)).

Note that the issue already exists today with readonly properties: those are basically private(set); but if you redeclare a non-private readonly property in a subclass, you can in fact initialise it from the subclass bypassing the initial private(set) restriction of the superclass: Online PHP editor | output for 9AV4r

Yes, thank you; that's a good point.
Seems like another issue with readonly, but then again, the
private(set) part of readonly is not really explicitly designed, I
guess.

If you want a property not to be overridable, end of story, you can mark it as `final` (the final marker for properties was added as part of the hooks RFC, but it works also with non-hooked properties).

Yes, it seems like a good enough option, and use "final public
private(set)" to ensure only the current class will be able to set the
value.
If we all agree, I think this should be documented in the RFC.

There is another small problem, I think, with "final" modifier not
being allowed for constructor-promoted properties.
And maybe we can have this fixed in the current RFC if this ends up
being "the correct" way to define public-read private-write properties.

Regards,
Alex

Mm, yeah, this is an interesting corner case we'd not thought of. We spent a little time looking at how other languages handle this.

Kotlin just disallows using private-set on a non-final property. (Kotlin properties are final by default, unless otherwise specified.) cf: Kotlin language specification

C# disallows changing property visibility in child classes entirely. cf: Restricting Accessor Accessibility - C# | Microsoft Learn

Swift is... weird. If you have a public private(set) property, you can override it by turning it into a virtual property with only a get hook. If you also define a set hook, though, it just never gets called. It doesn't appear that you can widen the set visibility, I think.

So since we now have final properties (yay, hooks RFC!), we could probably mostly solve this by just only allowing private(set) on a final property, like Kotlin. That's probably the easiest approach. We can also make private(set) properties implicitly final to avoid lots of boilerplate. (Having to always type final with private(set) every time is kinda silly.)

It would mean that proxy objects implemented using child classes with hooks would only work with a public protected(set) class, not with a private(set), but that's probably fine, and since no one is doing that quite yet, obviously, there's no existing code to be concerned about.

However, this also brings up another interesting issue: readonly properties (in 8.3) DO allow redeclaration, essentially adjusting the property scope (the class that declares it) to make the visibility check pass. That is, the definition of the class it is private to changes, which is different from how inheritance works elsewhere. When the parent writes to the same property, a special check is needed to verify the two properties are related. All that special casing effectively means that readonly in 8.4 wouldn't really be "write once + private(set)", but "write once + private(set) - final", which is... just kinda screwy. That means our options are:

* A BC break on readonly (not allowing it to be overridden)
* Make readonly an exception to the implicit final.
* Just don't allow readonly with aviz after all.

After some consideration, we believe the third option is best (least bad). The other options add still-more special cases or break existing code, neither of which are good. If a property is declared private(set), then child classes simply should not be allowed to change that, period. Even adding a get hook in a child is potentially a problem, as it would change the "round trip" behavior in ways the parent class doesn't expect. Disallowing readonly and making private(set) implicitly final avoids the loopholes in the language that would violate that expectation.

With aviz, all the use cases for readonly are essentially covered already. If you have private(set), you can control when it gets written anyway, so if you want it to be write-once, it’s write once. If you have protected(set), you’re allowing a child to set it, so they can do so whenever they want anyway, and could be doing it out of order from the parent class to begin with.

So if you wanted the equivalent of "public protected(set) readonly string $foo", in practice, the readonly is not giving you much to begin with. And "public public(set) readonly string $foo"... we just can't really think of a good use case for. Even if one exists, that could be emulated with a set hook now.

In terms of code length, thanks to the optional public-get visibility, the amount of typing is roughly the same:

readonly class ReadonlyPoint {
    public function __construct(
        public int $x,
        public int $y,
    ) {}
}

class AvizPoint {
    public function __construct(
        private(set) int $x,
        private(set) int $y,
    ) {}
}

(Or protected, if you prefer.) The latter provides a lot more flexibility, at the cost of "enforce write-once yourself in the class if you care", which seems an entirely reasonable tradeoff.

So we feel the best way forward is to make the following changes:

* private(set) implicitly means "final". (You can declare it explicitly if you want, but it isn't necessary.)
* Make readonly incompatible with aviz again.

Thoughts?

Also, Alexandru noted earlier that final properties don't seem to be supported in constructor promotion. That's an oversight from the hooks implementation, not a design choice, so Ilija will just fix that as part of the hooks PR before it gets fully merged.

--Larry Garfield

Le 31 mai 2024 à 18:08, Larry Garfield larry@garfieldtech.com a écrit :

However, this also brings up another interesting issue: readonly properties (in 8.3) DO allow redeclaration, essentially adjusting the property scope (the class that declares it) to make the visibility check pass. That is, the definition of the class it is private to changes, which is different from how inheritance works elsewhere. When the parent writes to the same property, a special check is needed to verify the two properties are related. All that special casing effectively means that readonly in 8.4 wouldn’t really be “write once + private(set)”, but “write once + private(set) - final”, which is… just kinda screwy. That means our options are:

  • A BC break on readonly (not allowing it to be overridden)
  • Make readonly an exception to the implicit final.
  • Just don’t allow readonly with aviz after all.

Another possible option is:

  • Make readonly be protected(set) by default.

That would weaken the originally intended semantics of readonly, but in a compatible and acceptable way?

—Claude

On Fri, May 31, 2024, at 5:45 PM, Claude Pache wrote:

Le 31 mai 2024 à 18:08, Larry Garfield <larry@garfieldtech.com> a écrit :

However, this also brings up another interesting issue: readonly properties (in 8.3) DO allow redeclaration, essentially adjusting the property scope (the class that declares it) to make the visibility check pass. That is, the definition of the class it is private to changes, which is different from how inheritance works elsewhere. When the parent writes to the same property, a special check is needed to verify the two properties are related. All that special casing effectively means that readonly in 8.4 wouldn't really be "write once + private(set)", but "write once + private(set) - final", which is... just kinda screwy. That means our options are:

* A BC break on readonly (not allowing it to be overridden)
* Make readonly an exception to the implicit final.
* Just don't allow readonly with aviz after all.

Another possible option is:

* Make readonly be `protected(set)` by default.

That would weaken the originally intended semantics of readonly, but in
a compatible and acceptable way?

—Claude

Only sort of compatible. It would allow readonly props to be redefined, and wouldn't break any existing code, I think... but it would mean any time you use readonly, suddenly a child class can come along and mess with it out from under you.

In practice that's likely not a common concern, but it is a behavior change. I think it's possible (I need to confirm with Ilija), if we want that slight BC break, but I don't know if we do.

--Larry Garfield

On Fri, May 31, 2024 at 9:13 PM Claude Pache <claude.pache@gmail.com> wrote:

Le 31 mai 2024 à 18:08, Larry Garfield <larry@garfieldtech.com> a écrit :

However, this also brings up another interesting issue: readonly properties (in 8.3) DO allow redeclaration, essentially adjusting the property scope (the class that declares it) to make the visibility check pass. That is, the definition of the class it is private to changes, which is different from how inheritance works elsewhere. When the parent writes to the same property, a special check is needed to verify the two properties are related. All that special casing effectively means that readonly in 8.4 wouldn't really be "write once + private(set)", but "write once + private(set) - final", which is... just kinda screwy. That means our options are:

* A BC break on readonly (not allowing it to be overridden)
* Make readonly an exception to the implicit final.
* Just don't allow readonly with aviz after all.

Another possible option is:

* Make readonly be `protected(set)` by default.

That would weaken the originally intended semantics of readonly, but in a compatible and acceptable way?

—Claude

I know this doesn't really contribute to the conversation ... but if I
could ever mash a +1 on a single email, this is the email I'd choose.

"Best elegant solution that happens to delete readonly without
deleting readonly" award.

Robert Landers
Software Engineer
Utrecht NL

Le 31 mai 2024 à 18:08, Larry Garfield larry@garfieldtech.com a écrit :

So we feel the best way forward is to make the following changes:

  • private(set) implicitly means “final”. (You can declare it explicitly if you want, but it isn’t necessary.)
  • Make readonly incompatible with aviz again.

Thoughts?

After reflection, I don’t think that we need to make readonly incompatible with aviz, even with the current semantics of readonly (at least logically; no idea about implementationally):

  • legacy-readonly properties could keep their own peculiar private-overridable(set) if they want;
  • aviz-readonly properties have, by definition, one of public(set), protected(set) or private(set) marker; those will work regularly, including the implicit final attached to private(set);
  • a non-aviz readonly property could not be redeclared in a subclass as aviz-readonly, and vice versa.

—Claude

So we feel the best way forward is to make the following changes:

  • private(set) implicitly means “final”. (You can declare it explicitly if you want, but it isn’t necessary.)
  • Make readonly incompatible with aviz again.

I’d make readonly incompatible with aviz. Readonly props have its “peculiarities” that are being (found and) changed on each version.
If you don’t want to change your value ever, use readonly.
If you want more flexibility, use aviz.

Making workarounds on the aviz RFC so it can be compatible with readonly is not worth it, IMO.

Best regards,
Erick

Em sex., 31 de mai. de 2024 às 20:53, Claude Pache <claude.pache@gmail.com> escreveu:

Le 31 mai 2024 à 18:08, Larry Garfield <larry@garfieldtech.com> a écrit :

So we feel the best way forward is to make the following changes:

  • private(set) implicitly means “final”. (You can declare it explicitly if you want, but it isn’t necessary.)
  • Make readonly incompatible with aviz again.

Thoughts?

After reflection, I don’t think that we need to make readonly incompatible with aviz, even with the current semantics of readonly (at least logically; no idea about implementationally):

  • legacy-readonly properties could keep their own peculiar private-overridable(set) if they want;
  • aviz-readonly properties have, by definition, one of public(set), protected(set) or private(set) marker; those will work regularly, including the implicit final attached to private(set);
  • a non-aviz readonly property could not be redeclared in a subclass as aviz-readonly, and vice versa.

—Claude

On Fri, May 31, 2024 at 7:13 PM Larry Garfield <larry@garfieldtech.com> wrote:

So we feel the best way forward is to make the following changes:

  • private(set) implicitly means “final”. (You can declare it explicitly if you want, but it isn’t necessary.)
  • Make readonly incompatible with aviz again.

Thoughts?

I think making properties final when using private(set) is a good solution to this.

I don’t think readonly needs to be incompatible with aviz. We can have readonly act as private(set) but not final by default.
But you can define it as private(set) readonly, and in this case it will be final, practically the same as final readonly.
It would go like this:

  • private(set) → final, private(set)

  • final private(set) → final, private(set)

  • readonly → write-once, private(set)

  • final readonly → final, write-once, private(set)

  • private(set) readonly → final, write-once, private(set)

  • protected(set) readonly → write-once, protected(set)

  • public(set) readonly → write-once, public(set)

Also, Alexandru noted earlier that final properties don’t seem to be supported in constructor promotion. That’s an oversight from the hooks implementation, not a design choice, so Ilija will just fix that as part of the hooks PR before it gets fully merged.

Maybe we need to have some discussion/considerations about allowing new modifiers on constructor promoted properties parameters.
Right now there is no final modifier for parameters, but this means we might not be able to allow them in the future.

In other languages (Java), it makes the parameter variable a constant (not reassignable).

So this decision might have implications so that when we decide to make variables or parameters not reassignable, we will not be able to use “final” as a keyword.
Not saying there is a problem, just that we need to be aware of it, as probably final is not a very good choice, and const or readonly are probably better options.

Alex

Hey Larry, hey Ilija

Am 29.05.24 um 21:15 schrieb Larry Garfield:

As promised, Ilija and I offer this revised version of asymmetric visibility.

PHP: rfc:asymmetric-visibility-v2

It's still essentially the same as last year's version, but with a few adjustments and changes:

* readonly properties are now supported in a logical fashion.
* We've brought back the abbreviated form, as public-read, something else set is the most common use case.
* The section on magic methods has been greatly simplified. The implementation itself hasn't changed, but the explanation is a lot less confusing now.
* We've explained how aviz interacts with hooks (they don't, really) and with interface properties (in the obvious way), which didn't exist at the time of the last draft.
* We've added a section with examples of how aviz is a concrete improvement, even in a world with readonly and hooks.
* We've added a section discussing why the prefix-style syntax was chosen.

*dons flame retardant suit*

In general I do like the RFC. No need for a flame retardand suit IMO :wink:

There is only one thing that I stumbled upon which struck me as odd:

> The set visibility, if specified explicitly, MUST be equal to or
> lesser than the main (get) visibility. That is, protected public(set)
> string $foo is not allowed.

Why?

Why can we not set a property as publicly writable but unreadable?

If that is a technical necessity, then so be it. But if that is a logical limitation, then I'm asking myself: why do we want to explicitly limit possible usecases? Not that I have one right now in my mind but I do know that we used similar settings in Filesystems for letterbox systems where users could copy files into other users letterbox (write only) without being able to later see the files.

Similar possibilities are available here here a property could be publicly writeable but only privately readable as read-access is only granted via a method.

I'm not saying it's the usualy use-case, but why explicitly disallowing it?

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 |
+---------------------------------------------------------------------+
| GPG-Key: https://hei.gl/keyandreasheiglorg |
+---------------------------------------------------------------------+