Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128172 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 835611A00BC for ; Tue, 22 Jul 2025 07:43:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1753170123; bh=+IY8Ozc0koPAYn8bU5mgfSWsb1BI0yR/f1SkA7B8jYg=; h=Date:From:To:Cc:In-Reply-To:References:Subject:From; b=Ju2Y+jjz/epfz6hEtsTYmWrui6i5AXJlvhrnWYvjOL+uK02wc+Zo21BHAu6EKb+Ly T56pBv1AJxmSPK92xrwjLbfhubnJ1IwGmk2qqZTFil/pEK0T4Upve0wRn3XzraPD35 NX0usKAJSi0uBWel9kGXiET939nkuzDHtubf6uoUsRpOSnjKB7i9yhvrZjkZgQbpVi +NG65LuyKRKrYHN0csjTo5J2nCGu7kYofYUM/a6tlFiGQqMEucDlnFPLBIUNninjAg tJxModdr8bjhJC5xRic6Wc2CxxRDSnrgUs5LmYWJgAEpifeM5Rp1EaJ5vVvi12b2tl X2I6Q5PhtFuMQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 11513180074 for ; Tue, 22 Jul 2025 07:42:02 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from fhigh-a6-smtp.messagingengine.com (fhigh-a6-smtp.messagingengine.com [103.168.172.157]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 22 Jul 2025 07:42:01 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailfhigh.phl.internal (Postfix) with ESMTP id 777711400149; Tue, 22 Jul 2025 03:43:46 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Tue, 22 Jul 2025 03:43:46 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:cc:content-type:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm3; t=1753170226; x= 1753256626; bh=Jx0JM4Joh3eI5C0G/m7cl/lu14JB7j2IWveiyWVdBnw=; b=R cx8dxnh24PP00qqGyuB9hkVp0QohHQRjaYzYDsAcL3fZvkFd3rGwUD1QWr81s6Iv oRhEhNYZ9FPObOC0Ez5G4CmUhdzPvWWDCbl9VeDV2USC5ipT2AdRlFTap2/0k5of 15s5HUSuH+sJO+tYyECRXUdv6kpG3qesvYZlGAevYdP6xyGqETtpZg66BO8JwE4Y 8MFMl4M077IJ3msH0TA/eOVP1HhE0/tg5HXBcwiC2AtmxuI6coceTSvHDzX9bBEc AEpzJzaWuvv0SB0vIA/c6w+U4yybsTjGnGsSl2W9lUJQ2WgD1alq4XrRwaxRmfVp TYGSq+nmxiHtjfZYrzXIw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1753170226; x=1753256626; bh=Jx0JM4Joh3eI5C0G/m7cl/lu14JB7j2IWve iyWVdBnw=; b=fxl9eCgg1MSts1YKUVlTkO1r0vWNK8S3uBy0aXJjFdCf4auB9Ma /p3dmTrV9o5B2HekcQJsQ4tfZkYhhWnEhBEPA4c4d9pydAXq0qxuRMCsInWJ3Xu2 M8vnQxEYXV2k3PGFfHByEp9YCXmGGmpNi7rHSOCmih80b9GGqeJwYILALM9ziArw oeacyTvYqCVR5WKH0dOspvQgF53sHtKUnF3PHlpnKxEtDU9prMxBDHLi5TPkVdW8 u5Z+KF4nPsEi+tAuklAJsgq70Vx6hphMVFM4ntqNyqrISVdu7/0OGbQOVGiV9VXw mgefVNxWZgkujEqPqvl/n4eh49WMu7Hbprg== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdefgdejgeefvdcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjug hrpefoggffhffvvefkjghfufgtsegrtderreertdejnecuhfhrohhmpedftfhosgcunfgr nhguvghrshdfuceorhhosgessghothhtlhgvugdrtghouggvsheqnecuggftrfgrthhtvg hrnhepjefhffegleeiudejfedvveeftedtheffjeegfeffvedtvdeivdeuteekfeetkedu necuffhomhgrihhnpeefvheglhdrohhrghdpphhhphdrnhgvthenucevlhhushhtvghruf hiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegsohhtthhlvggurdgt ohguvghspdhnsggprhgtphhtthhopeefpdhmohguvgepshhmthhpohhuthdprhgtphhtth hopegtlhgruhguvgdrphgrtghhvgesghhmrghilhdrtghomhdprhgtphhtthhopegvrhhi tghtnhhorhhrihhssehgmhgrihhlrdgtohhmpdhrtghpthhtohepihhnthgvrhhnrghlsh eslhhishhtshdrphhhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id F0DF1182007A; Tue, 22 Jul 2025 03:43:45 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: T0d65545c0179fcf6 Date: Tue, 22 Jul 2025 09:43:25 +0200 To: erictnorris@gmail.com Cc: "Claude Pache" , "php internals" Message-ID: <4567d926-aaed-44a8-9cd6-68e8e12742f4@app.fastmail.com> In-Reply-To: References: <378cd8b0-c4a8-4e53-b0bd-e9e4f30a454d@app.fastmail.com> <77128196-8D53-40D6-9BB7-77160AD71ED9@gmail.com> <247034b6-04cf-4f3e-a145-a30171af8c12@app.fastmail.com> <5a51401a-450b-46f0-8467-88d31efecb9c@app.fastmail.com> Subject: Re: [PHP-DEV] [RFC] Readonly property hooks Content-Type: multipart/alternative; boundary=1978c3a9bd3e42c49cd053b2a949eb02 From: rob@bottled.codes ("Rob Landers") --1978c3a9bd3e42c49cd053b2a949eb02 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Tue, Jul 22, 2025, at 02:10, Eric Norris wrote: > On Sun, Jul 20, 2025 at 4:19=E2=80=AFPM Rob Landers wrote: > > > > > > > > On Sun, Jul 20, 2025, at 19:18, Eric Norris wrote: > > > > Hi Rob, > > > > I'm going to respond to a few points from earlier emails here instead > > of each one. > > > > On Sat, Jul 19, 2025 at 6:13=E2=80=AFAM Rob Landers wrote: > > > > > > > > > > > > On Sat, Jul 19, 2025, at 12:09, Claude Pache wrote: > > > > > > > > > > > > Le 19 juil. 2025 =C3=A0 09:46, Rob Landers a =C3= =A9crit : > > > > > > > > > > > > On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote: > > > > > > > > > > > > > > > Le 19 juil. 2025 =C3=A0 00:41, Rob Landers a =C3= =A9crit : > > > > > > > > > The original author (Nikita) suggested that there's nothing in the= original design that precludes accessors -- and highlights languages wh= ere there are both and they are doing just fine more than 5 years later. > > > > > > > > > Hi Rob, > > > > > > It is indeed entirely reasonable to have both readonly properties = and hooked properties (aka accessors), and today PHP has indeed both of = them (and even asymmetric visibility on top of that, as a separate featu= re contrarily to C#). But it doesn=E2=80=99t mean that it is reasonable = for the *same* property to be *both* readonly and hooked, which is the p= oint that is currently disputed. =E2=80=94 What do the other languages a= llow? Is it possible to define a readonly property with a user-defined g= etter? (Disclaimer: Even if one of them allows such a thing, I=E2=80=99l= l still think that it is a bad idea.) > > > > I do not believe I was cherry picking; I share Claude's interpretati= on > > here that the RFC says that readonly doesn't prevent the language fr= om > > adopting accessors (hooks) later, which is what the language did, and > > notably it did without applying them to readonly properties. > > > > Could you perhaps walk me through your thinking when the RFC claims > > that `assert($prop =3D=3D=3D $prop2)` "always holds"? > > > > > > Hi Eric, > > > > I think that is explaining how Nikita arrived at some of the conclus= ions (it is in the rationale section, after all) and shouldn't be taken = as literal. Here are some snippets that I think should be looked at a li= ttle more closely, that seem to align with the vision of the feature, in= the context of getters/setters: > > > > "The closest alternative is to declare the property private, and onl= y expose a public getter" -- indicates to me that a single public getter= should, in fact, be considered readonly. >=20 > The very next line says, "This doesn't actually make the property > readonly, but it does tighten the scope where modification could occur > to a single class declaration." I interpret this as "the property can > still be written to *internally* many times, but at least you don't > have to look very far to confirm that this doesn't happen". I think the intent was to show how public readonly could be implemented = at that time. However, if you want private readonly, you cannot have tha= t in the version of PHP at that time. > Or to put it another way, the RFC is claiming that this makes it so > that a *consumer* cannot mutate the value freely, only the class can, > but *this is not in fact readonly*. It takes the position that the class itself can be the consumer and prov= ides a way to express it through write-once properties. > > "Support for first-class readonly properties allows you to directly = expose public readonly properties, without fear that class invariants co= uld be broken through external modification" -- indicates to me that int= erior mutability (and maybe nondeterminism) should be at the discretion = of the interior, not the exterior contract. >=20 > I think this is building on top of the above, indicating that readonly > allows it to be clear that no one can write to this property multiple > times, even though it is publicly exposed. It doesn't say "no one" it only says "class invariants" which may mean d= ifferent things in different designs and systems. If your design specifi= es that a value is invariant, that could mean it is "always constant", o= r on the other extreme, could mean it is "always random". When the desig= n aligns with readonly, then you can have the engine enforce it. > > "...readonly properties do not preclude interior mutability. Objects= (or resources) stored in readonly properties may still be modified inte= rnally" -- further specifies that interior mutability is allowed; only e= xterior mutability isn't. > > > > It doesn't define "interior mutability" stringently, so we can have = differing opinions on that; but it seems to be "the value inside the pro= perty" which may or may not be an object, resource, or a hook's result. >=20 > I do have a differing opinion, and will quote the first line of the > RFC - "This RFC introduces a readonly property modifier, which > prevents modification of the property after initialization." It seems > pretty clear to me that this is talking about ensuring that a property > is initialized once and the property *itself* will not change, but if > the property is an object, the object's properties are free to change. >=20 > I agree that the readonly keyword itself leaves room for > interpretation as I mentioned in my earlier email, but I just can't > seem to read this RFC as anything other than promising immutability, > especially considering this first line. You can also implement this today, without using readonly: class Foo { public int $bar { get =3D> $this->bar; set =3D> empty($this->bar) ? $this->bar =3D $value : throw new Logic= Exception("nope"); } } https://3v4l.org/2JagR#v8.4.10 Should I be able to mark this class as readonly? I would think so. That'= s the question this new RFC is proposing, in my mind. It is saying that = hooks are allowed to be readonly, allowing me to specify my own invarian= ts, to a degree, and having the engine enforce them where it can while a= lso signifying consumers there are some obvious invariants. Sure, you co= uld do other shenanigans in the get-hooks, but it is a programming langu= age -- there will always be shenanigans. > > > > > > =E2=80=94Claude > > > > > > > > > Hey Claude, > > > > > > From what I've seen in other languages, this combination is fairly= common and not inherently poblematic. > > > > > > - C# allows get hooks with user-defined logic, even in readonly st= ructs. > > > - Kotlin uses val with a get() body, which is readonly from the co= nsumer's perspective, even though the value is computed. > > > - TypeScript allows a get-only accessor which acts readonly. > > > - Swift also allows get-only computed accessors/hooks. > > > > (disclaimer, I am not a C# expert) > > > > It seems that C# has both fields and properties, and a readonly field > > seems to align with what a few of us are claiming is how PHP readonly > > properties should work. C# properties are more open-ended, and don't > > actually support the readonly keyword - you can make them "read only" > > in the sense of only having a get accessor (and you can mark that > > accessor itself as readonly), but this is different from readonly > > fields, which enforce a contract about the mutability of the field. > > > > I think that C# fields, both readonly and not, match PHP's properties > > without hooks. C# properties - which I believe cannot be *marked* > > readonly, but they can be *made* read only, i.e. only exposing a get > > accessor - match PHP's properties with hooks. > > > > > > I would prefer to simply allow specifying a class as readonly so lon= g as only get hooks are present. However, I'm ok with saying that get ho= oks may themselves be readonly which accomplishes the same thing and is = easier to reason about. >=20 > I'm sorry, I don't follow the second sentence. My main point was that > C#'s readonly *fields* cannot have accessors, which I believe are 1:1, > invariant-wise, with PHP's readonly properties. C#'s properties are > 1:1 with PHP's properties with hooks. >=20 > C# does have readonly accessors for properties, but this is a separate > invariant that makes the compiler ensure that the get acessor itself > cannot modify the state of the object, and is separate from the notion > of a readonly field. Correct! Yes, sorry. I didn't explain well. A readonly class in PHP is e= ffectively copy-pasting readonly before every property. This means we ei= ther need to redefine what that means, or allow readonly to be defined o= n hooks so that a class using hooks may be defined readonly in special c= ircumstances. > > > > > > > > > Hi Rob, > > > > > > The main problem is that we don=E2=80=99t agree on the meaning of = =E2=80=9Creadonly property=E2=80=9D. > > > > I agree with Claude here. Rob, I'm curious how you interpret the > > contract of readonly. Do you think that it is a contract about > > writability to consumers of the class? In an earlier email, you said: > > > > > In the example above, there is nothing mutable about it. It is "re= ad only" from a public contract point-of-view, we just can't mark it as = a readonly class. > > > > In this most recent email, you said: > > > > > The error you get when trying to modify the "get-only accessor" is= `Error: Cannot assign to 'name' because it is a read-only property` > > > > > > Thus, readonly is implied by the get-only accessor, there's no nee= d to specify it directly. > > > > Would you say this is how you are interpreting readonly? That it is a > > contract to consumers of the class that they can read but cannot wri= te > > to it? > > > > > > That's the definition in the RFC, from my reading of it: the ability= to expose bare properties without worrying that someone else will modif= y it. One might argue that there really isn't a reason for readonly anym= ore, since we have aviz + hooks. Or rather, that readonly could be just = a shorthand for that -- plus the ability to write to those properties ex= actly-once. I suspect this is where "write-once" is coming from elsewher= e in the thread, since that is the only difference between "manual reado= nly" and "engine-powered readonly". >=20 > I would not argue that there isn't a reason for readonly anymore due > to aviz, I would argue that readonly means something different from > aviz, which is exactly how I read the RFC. >=20 > Can I ask what you would like to use the readonly keyword for? I would > like to use it to, as the RFC states, "[prevent] modification of the > property after initialization". Would you also like to use it to > prevent modification of the property after initialization, or would > you like to use it to signify that it is `public protected(set)`? The > latter, to me, is an implementation detail of readonly and not the > main point; if you want to declare asymmetric visibility, use > asymmetric visibility. The problem I've run into, especially in 8.3+ is that I like value objec= ts. Ever since reading Domain Driven Design by Eric Evans in 2006-07, I = love how simply using them can remove a ton of procedural code, enforce = invariants, and make reasoning through code so much simpler (see Chapter= 10). The readonly keyword simplified that greatly, however, readonly ha= s been neutered compared to regular classes in the last couple of versio= ns. There are so many edge cases and non-implemented features with them = -- mostly due to this exact argument you are making -- that they're near= ly worthless to actually define immutable value objects in today's PHP. = This is why I ever even began with the Records RFC ... between all the e= dge cases and weird behaviors, I want to create "actually immutable valu= e objects" that are just as powerful as regular classes and leave readon= ly for when you need immutable parts of classes. > > (including a snippet from an earlier email) > > > > > I think an init hook is out of the question for 8.5, so I'm not ev= en sure it's worth discussing. > > > > I don't agree that it's not worth discussing alternative solutions to > > the problems the RFC authors intend to solve. I think it has been > > expressed elsewhere, but I believe we should take the time to make t= he > > best decisions for the language and not rush to add a controversial > > feature. > > > > > > Would an init hook actually solve it though? An init hook would basi= cally just be a constructor / default value outside of the constructor t= hat specifies an instance-level constant. The readonly property RFC goes= into some details here: > > > > "As the default value counts as an initializing assignment, a readon= ly property with a default value is essentially the same as a constant, = and thus not particularly useful. The notion could become more useful in= the future, if new expressions are allowed as property default values. = At the same time, depending on how exactly property initialization would= work in that case, having a default value on a readonly property could = preclude userland serialization libraries from working, as they would no= t be able to replace the default-constructed object. Whether or not this= is a concern depends on whether the property is initialized at time of = object creation, or as an implicit part of the constructor (or similar).= As these are open questions, the conservative choice is to forbid defau= lt values until these questions are resolved." >=20 > An init hook would not be a default value outside of the constructor > that specifies an instance level constant, what gives you that > impression? The problem that people have pointed to is lazy > initialization, e.g. fetching something from the database when > accessed. This very RFC mentions lazy initialization as the use-case > for get hooks here: > https://wiki.php.net/rfc/readonly_hooks#orms_and_proxies. >=20 > I believe that an init hook would solve this problem instead, and > maintain the invariant of immutability for readonly properties. >=20 > > When would the init hook get called? And in what order? This brings = back some memories of solving some Java language bugs where static varia= bles wouldn't be initialized in time (they're non-deterministic) causing= strange crashes, or the potential to "deadlock" yourself and you need t= o read properties in a specific order in order to ensure the object gets= initialized correctly. >=20 > Tim and I separately offered answers to these questions earlier; in > any case I believe it's a tractable problem if our initial suggestions > aren't enough. >=20 > > This isn't something that can be solved in a few weeks. Someone(s) n= eeds to sit down and think through all the possibilities and then create= a definition of an init hook that is better than a constructor. >=20 > I agree this might not be solved in a few weeks, but since I don't > think we need to rush this before 8.5, I don't see this as a problem. I honestly haven't read the arguments for init-hooks. Mostly because I s= aw that part of the conversation and it reminds me of when other people = start proposing their ideas in other people's RFCs. Sometimes they're go= od ideas, sometimes their not, but I leave that to the author to digest = and make changes to the RFC. I don't agree with init-hooks for the reaso= ns I gave, but until the RFC author incorporates it into the RFC, I tend= to ignore those types of things. The author is the subject-matter exper= t and they're the ones that know if something is a good idea or not. Eng= aging in those parts of the discussions just leads to off-topic discussi= ons. =E2=80=94 Rob --1978c3a9bd3e42c49cd053b2a949eb02 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable


On Tue, Jul 22, 2025, at 02:10, Eric Norris wrote:
On Sun, Jul 20, 20= 25 at 4:19=E2=80=AFPM Rob Landers <rob@bottled.codes> wrote:
>
>
<= div>>
> On Sun, Jul 20, 2025, at 19:18, Eric Norris wrot= e:
>
> Hi Rob,
>
> I= 'm going to respond to a few points from earlier emails here instead
> of each one.
>
> On Sat, Jul 19, 2= 025 at 6:13=E2=80=AFAM Rob Landers <rob@bottled.codes> wrote:
> >
> = >
> >
> > On Sat, Jul 19, 2025, at 12= :09, Claude Pache wrote:
> >
> >
> >
> > Le 19 juil. 2025 =C3=A0 09:46, Rob Land= ers <rob@bottled.codes> a= =C3=A9crit :
> >
> >
> >= ;
> > On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote= :
> >
> >
> >
= > >
> > Le 19 juil. 2025 =C3=A0 00:41, Rob Landers= <rob@bottled.codes> a =C3= =A9crit :
> >
> >
> > Th= e original author (Nikita) suggested that there's nothing in the origina= l design that precludes accessors -- and highlights languages where ther= e are both and they are doing just fine more than 5 years later.
> >
> >
> > Hi Rob,
&= gt; >
> > It is indeed entirely reasonable to have bo= th readonly properties and hooked properties (aka accessors), and today = PHP has indeed both of them (and even asymmetric visibility on top of th= at, as a separate feature contrarily to C#). But it doesn=E2=80=99t mean= that it is reasonable for the *same* property to be *both* readonly and= hooked, which is the point that is currently disputed. =E2=80=94 What d= o the other languages allow? Is it possible to define a readonly propert= y with a user-defined getter? (Disclaimer: Even if one of them allows su= ch a thing, I=E2=80=99ll still think that it is a bad idea.)
&= gt;
> I do not believe I was cherry picking; I share Claude= 's interpretation
> here that the RFC says that readonly do= esn't prevent the language from
> adopting accessors (hooks= ) later, which is what the language did, and
> notably it d= id without applying them to readonly properties.
>
> Could you perhaps walk me through your thinking when the RFC clai= ms
> that `assert($prop =3D=3D=3D $prop2)` "always holds"?<= /div>
>
>
> Hi Eric,
>
> I think that is explaining how Nikita arrived at some of the= conclusions (it is in the rationale section, after all) and shouldn't b= e taken as literal. Here are some snippets that I think should be looked= at a little more closely, that seem to align with the vision of the fea= ture, in the context of getters/setters:
>
> "= The closest alternative is to declare the property private, and only exp= ose a public getter" -- indicates to me that a single public getter shou= ld, in fact, be considered readonly.

The very n= ext line says, "This doesn't actually make the property
readon= ly, but it does tighten the scope where modification could occur
to a single class declaration." I interpret this as "the property can=
still be written to *internally* many times, but at least you= don't
have to look very far to confirm that this doesn't happ= en".

I think the intent was to sho= w how public readonly could be implemented at that time. However, if you= want private readonly, you cannot have that in the version of PHP at th= at time.

Or to put it another way, the RFC is claiming that this makes it= so
that a *consumer* cannot mutate the value freely, only the= class can,
but *this is not in fact readonly*.

It takes the position that the class itself can = be the consumer and provides a way to express it through write-once prop= erties.

> "Support for first-class readonly properties allows you to = directly expose public readonly properties, without fear that class inva= riants could be broken through external modification" -- indicates to me= that interior mutability (and maybe nondeterminism) should be at the di= scretion of the interior, not the exterior contract.

I think this is building on top of the above, indicating that read= only
allows it to be clear that no one can write to this prope= rty multiple
times, even though it is publicly exposed.
<= /blockquote>

It doesn't say "no one" it only says "cl= ass invariants" which may mean different things in different designs and= systems. If your design specifies that a value is invariant, that could= mean it is "always constant", or on the other extreme, could mean it is= "always random". When the design aligns with readonly, then you can hav= e the engine enforce it.

> "...readonly properties do not preclude in= terior mutability. Objects (or resources) stored in readonly properties = may still be modified internally" -- further specifies that interior mut= ability is allowed; only exterior mutability isn't.
>
=
> It doesn't define "interior mutability" stringently, so we can= have differing opinions on that; but it seems to be "the value inside t= he property" which may or may not be an object, resource, or a hook's re= sult.

I do have a differing opinion, and will q= uote the first line of the
RFC - "This RFC introduces a readon= ly property modifier, which
prevents modification of the prope= rty after initialization." It seems
pretty clear to me that th= is is talking about ensuring that a property
is initialized on= ce and the property *itself* will not change, but if
the prope= rty is an object, the object's properties are free to change.
=
I agree that the readonly keyword itself leaves room for<= /div>
interpretation as I mentioned in my earlier email, but I just = can't
seem to read this RFC as anything other than promising i= mmutability,
especially considering this first line.

You can also implement this today, without = using readonly:

class Foo {
  pu= blic int $bar {
    get =3D> $this->bar;
=
    set =3D> empty($this->bar) ? $this->bar =3D = $value : throw new LogicException("nope");
  }
= }


Should I be ab= le to mark this class as readonly? I would think so. That's the question= this new RFC is proposing, in my mind. It is saying that hooks are allo= wed to be readonly, allowing me to specify my own invariants, to a degre= e, and having the engine enforce them where it can while also signifying= consumers there are some obvious invariants. Sure, you could do other s= henanigans in the get-hooks, but it is a programming language -- there w= ill always be shenanigans.

> >
> > =E2=80=94Claude=
> >
> >
> > Hey Claude,=
> >
> > From what I've seen in other la= nguages, this combination is fairly common and not inherently poblematic= .
> >
> > - C# allows get hooks with use= r-defined logic, even in readonly structs.
> > - Kotlin = uses val with a get() body, which is readonly from the consumer's perspe= ctive, even though the value is computed.
> > - TypeScri= pt allows a get-only accessor which acts readonly.
> > -= Swift also allows get-only computed accessors/hooks.
>
> (disclaimer, I am not a C# expert)
>
= > It seems that C# has both fields and properties, and a readonly fie= ld
> seems to align with what a few of us are claiming is h= ow PHP readonly
> properties should work. C# properties are= more open-ended, and don't
> actually support the readonly= keyword - you can make them "read only"
> in the sense of = only having a get accessor (and you can mark that
> accesso= r itself as readonly), but this is different from readonly
>= ; fields, which enforce a contract about the mutability of the field.
>
> I think that C# fields, both readonly and no= t, match PHP's properties
> without hooks. C# properties - = which I believe cannot be *marked*
> readonly, but they can= be *made* read only, i.e. only exposing a get
> accessor -= match PHP's properties with hooks.
>
>
<= div>> I would prefer to simply allow specifying a class as readonly s= o long as only get hooks are present. However, I'm ok with saying that g= et hooks may themselves be readonly which accomplishes the same thing an= d is easier to reason about.

I'm sorry, I don't= follow the second sentence. My main point was that
C#'s reado= nly *fields* cannot have accessors, which I believe are 1:1,
i= nvariant-wise, with PHP's readonly properties. C#'s properties are
=
1:1 with PHP's properties with hooks.

C# d= oes have readonly accessors for properties, but this is a separate
=
invariant that makes the compiler ensure that the get acessor itsel= f
cannot modify the state of the object, and is separate from = the notion
of a readonly field.

Correct! Yes, sorry. I didn't explain well. A readonly class in = PHP is effectively copy-pasting readonly before every property. This mea= ns we either need to redefine what that means, or allow readonly to be d= efined on hooks so that a class using hooks may be defined readonly in s= pecial circumstances.

> >
> >
> > H= i Rob,
> >
> > The main problem is that = we don=E2=80=99t agree on the meaning of =E2=80=9Creadonly property=E2=80= =9D.
>
> I agree with Claude here. Rob, I'm cu= rious how you interpret the
> contract of readonly. Do you = think that it is a contract about
> writability to consumer= s of the class? In an earlier email, you said:
>
= > > In the example above, there is nothing mutable about it. It is= "read only" from a public contract point-of-view, we just can't mark it= as a readonly class.
>
> In this most recent = email, you said:
>
> > The error you get wh= en trying to modify the "get-only accessor" is `Error: Cannot assign to = 'name' because it is a read-only property`
> >
> > Thus, readonly is implied by the get-only accessor, there's n= o need to specify it directly.
>
> Would you s= ay this is how you are interpreting readonly? That it is a
>= ; contract to consumers of the class that they can read but cannot write=
> to it?
>
>
> That= 's the definition in the RFC, from my reading of it: the ability to expo= se bare properties without worrying that someone else will modify it. On= e might argue that there really isn't a reason for readonly anymore, sin= ce we have aviz + hooks. Or rather, that readonly could be just a shorth= and for that -- plus the ability to write to those properties exactly-on= ce. I suspect this is where "write-once" is coming from elsewhere in the= thread, since that is the only difference between "manual readonly" and= "engine-powered readonly".

I would not argue t= hat there isn't a reason for readonly anymore due
to aviz, I w= ould argue that readonly means something different from
aviz, = which is exactly how I read the RFC.

Can I ask = what you would like to use the readonly keyword for? I would
l= ike to use it to, as the RFC states, "[prevent] modification of the
property after initialization". Would you also like to use it to
prevent modification of the property after initialization, or w= ould
you like to use it to signify that it is `public protecte= d(set)`? The
latter, to me, is an implementation detail of rea= donly and not the
main point; if you want to declare asymmetri= c visibility, use
asymmetric visibility.

The problem I've run into, especially in 8.3+ is that I= like value objects. Ever since reading Domain Driven Design by Eric Eva= ns in 2006-07, I love how simply using them can remove a ton of procedur= al code, enforce invariants, and make reasoning through code so much sim= pler (see Chapter 10). The readonly keyword simplified that greatly, how= ever, readonly has been neutered compared to regular classes in the last= couple of versions. There are so many edge cases and non-implemented fe= atures with them -- mostly due to this exact argument you are making -- = that they're nearly worthless to actually define immutable value objects= in today's PHP. This is why I ever even began with the Records RFC ... = between all the edge cases and weird behaviors, I want to create "actual= ly immutable value objects" that are just as powerful as regular classes= and leave readonly for when you need immutable parts of classes.
<= div>
> (= including a snippet from an earlier email)
>
>= > I think an init hook is out of the question for 8.5, so I'm not ev= en sure it's worth discussing.
>
> I don't agr= ee that it's not worth discussing alternative solutions to
>= ; the problems the RFC authors intend to solve. I think it has been
> expressed elsewhere, but I believe we should take the time to= make the
> best decisions for the language and not rush to= add a controversial
> feature.
>
&g= t;
> Would an init hook actually solve it though? An init h= ook would basically just be a constructor / default value outside of the= constructor that specifies an instance-level constant. The readonly pro= perty RFC goes into some details here:
>
> "As= the default value counts as an initializing assignment, a readonly prop= erty with a default value is essentially the same as a constant, and thu= s not particularly useful. The notion could become more useful in the fu= ture, if new expressions are allowed as property default values. At the = same time, depending on how exactly property initialization would work i= n that case, having a default value on a readonly property could preclud= e userland serialization libraries from working, as they would not be ab= le to replace the default-constructed object. Whether or not this is a c= oncern depends on whether the property is initialized at time of object = creation, or as an implicit part of the constructor (or similar). As the= se are open questions, the conservative choice is to forbid default valu= es until these questions are resolved."

An init= hook would not be a default value outside of the constructor
= that specifies an instance level constant, what gives you that
impression? The problem that people have pointed to is lazy
i= nitialization, e.g. fetching something from the database when
= accessed. This very RFC mentions lazy initialization as the use-case
for get hooks here:

I believe that an init = hook would solve this problem instead, and
maintain the invari= ant of immutability for readonly properties.

> When would the init hook get called? And in what order? This brin= gs back some memories of solving some Java language bugs where static va= riables wouldn't be initialized in time (they're non-deterministic) caus= ing strange crashes, or the potential to "deadlock" yourself and you nee= d to read properties in a specific order in order to ensure the object g= ets initialized correctly.

Tim and I separately= offered answers to these questions earlier; in
any case I bel= ieve it's a tractable problem if our initial suggestions
aren'= t enough.

> This isn't something that can be= solved in a few weeks. Someone(s) needs to sit down and think through a= ll the possibilities and then create a definition of an init hook that i= s better than a constructor.

I agree this might= not be solved in a few weeks, but since I don't
think we need= to rush this before 8.5, I don't see this as a problem.

I honestly haven't read the arguments for init-ho= oks. Mostly because I saw that part of the conversation and it reminds m= e of when other people start proposing their ideas in other people's RFC= s. Sometimes they're good ideas, sometimes their not, but I leave that t= o the author to digest and make changes to the RFC. I don't agree with i= nit-hooks for the reasons I gave, but until the RFC author incorporates = it into the RFC, I tend to ignore those types of things. The author is t= he subject-matter expert and they're the ones that know if something is = a good idea or not. Engaging in those parts of the discussions just lead= s to off-topic discussions.

=E2=80=94 Rob
--1978c3a9bd3e42c49cd053b2a949eb02--