Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128171 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 2BA481A00BC for ; Tue, 22 Jul 2025 00:10:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1753142945; bh=e8ysZV8fLgIxE3vvubbHXwE3YJByhovVPSaQqaUIIwA=; h=References:In-Reply-To:Reply-To:From:Date:Subject:To:Cc:From; b=lM3mTjnUWVZfxb7NA0lR9EKTep9shKt6MVx08WY6sHZw2KqP5HdPo0l3cdB6df3KT 54V/xXarjU7hnPw5Xkop16mXe4fPoKQ+sd327aavtSvXZG1dFhiAjra/J6uulGl+jA BHYo6Qoz8Qbksc8Pj24QZPIiUin7Jh49ickiCux7ka940wa7bVaQHgJFXZfqNQBapH NqnIXv+AMzf/fAGsxIQOrqkuRsSNmhVI296uAe1puXqiet2LXz34zEb8ZK4sskn6qu 623LQ5kIp/Z8HGGnBD4C/dNYbQNT7F6jQXHC2PUTP9xQPuNANkbQkbtpGdEL9qhWsx W6XkKHVSPXL7w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 4C0ED180087 for ; Tue, 22 Jul 2025 00:09:04 +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.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, FREEMAIL_REPLYTO,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, 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 mail-io1-f42.google.com (mail-io1-f42.google.com [209.85.166.42]) (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 00:09:03 +0000 (UTC) Received: by mail-io1-f42.google.com with SMTP id ca18e2360f4ac-87c0bb1ee4eso111853239f.2 for ; Mon, 21 Jul 2025 17:10:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1753143049; x=1753747849; darn=lists.php.net; h=content-transfer-encoding:cc:to:subject:message-id:date:from :reply-to:in-reply-to:references:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=e8ysZV8fLgIxE3vvubbHXwE3YJByhovVPSaQqaUIIwA=; b=h79QNXd9/cQVRs/ogV7XStMzN9MbS9YBYMltPx0q+BE+7ElxF14dKr4lWd80NXupku safMKEnHMMCPtWeVr7m42Ef1pnFgVpZGRKutujaagcnbe/T2thKv471UL2A49MrgmH/p k4CAQsgYEca1Kves7SUt4EHLh2ScwmUMRzLivuSDUDzh6JCDhspiGR/0zXJ0CJ6ChRZO skk1PKfYDwOTncx//iAdUtEKa4Gva7pcMSJrnV4S+ViDoARkzDQz24R0RGIGkA6WsXFb AG79lEpZibLoKmn8W/+nImtk1xEIlf3hve2GMoqU9nXVPun3O7cZyMOfiymdxUx9Cej+ klSQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1753143049; x=1753747849; h=content-transfer-encoding:cc:to:subject:message-id:date:from :reply-to:in-reply-to:references:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=e8ysZV8fLgIxE3vvubbHXwE3YJByhovVPSaQqaUIIwA=; b=qH5z39xbGfTNv9CoIwRLHI7hbvMjvNGRVPHjiJwu9ktm/kmnLGWPiR4Nuu2GzzOvea J6n+GDtnT7uBFuvbkMceBWjxH2jY9MsDbUjJzfKWUQYklKebBLhH5fcq2NscTVt8dxLL /cT86R1JolZ/DzAc1bYyoakzN1zRK/QSxLspRRPaYcjwMP4AR8BIKVoad8j8IhXFa0Tb G+gxLG+DDYwiDBLXYOWsSC2j6+Xn33MzxC64q/6r1UXZZHfC4+1hTdjsqQzKdwNyXCVi Psw53ojrRIb3kqx3AQ5Eh5yekQmXCdmRelvfjRe9RtLFj1kvqD7T9ZbsAkoim8FRcah8 3DWA== X-Forwarded-Encrypted: i=1; AJvYcCXTjUAobipg91wGzqiPhv1Zn51N+p7fG08oay2gYI8PI0f83tt35Haihl+IR0VuXZggmY917aSVbTo=@lists.php.net X-Gm-Message-State: AOJu0YzVHVixWWdvXQeOLvezYuk9j9nd2QWrIBxWoblFYlofPivkc4gw GIatFUvke8x89FP4fM4O9TqzEZGMiD4WeNLpFOx/yDlvaNaTNSk4C9wNR9Jl3It/tp1hVShsO1B lnYj7fpSRr3c40gKnVZUuxrUiZLFmYfQ= X-Gm-Gg: ASbGncushV7FG+PIw11R7dEZencCuh+5c/j7lgZ1Bbx82u+2htJtivfJn7FvDtAvFqd AtI7zs6cUZ1cv3Oej9jaP5fKcDnGVUbqp3VtCzGGZHKUwDql9b8khzzUr6ty3sCkzE/NlXhdMIh vOl2Vf3sG7LRN0ujrnLORNmU5wpdSRd0mr0c6krVWts8xDKQQONcpaXes6cgEzlicGGdcTg3M51 CybbKBEHyoUHxRz3KpIf3IFnvEi67CzTiPlG2hiAQ== X-Google-Smtp-Source: AGHT+IEBfOEbD2RUvKNny9Z95OdMUr36Q943Kmi2jErEYUQ7bVCyPSN3mInUS13SScmQ79XOxEYfdOIZ9ie6JY+DgLc= X-Received: by 2002:a05:6e02:2509:b0:3e2:aafc:a7f with SMTP id e9e14a558f8ab-3e2aafc10d7mr118110865ab.7.1753143048636; Mon, 21 Jul 2025 17:10:48 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 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> In-Reply-To: <5a51401a-450b-46f0-8467-88d31efecb9c@app.fastmail.com> Reply-To: erictnorris@gmail.com Date: Mon, 21 Jul 2025 20:10:32 -0400 X-Gm-Features: Ac12FXz1A10U4Xej4zxdBe8r_kDzS2l4OztZ3t9Xe9VLW0hyThFBni1Pg72rrsg Message-ID: Subject: Re: [PHP-DEV] [RFC] Readonly property hooks To: Rob Landers Cc: Claude Pache , php internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable From: eric.t.norris@gmail.com (Eric Norris) On Sun, Jul 20, 2025 at 4:19=E2=80=AFPM Rob Landers wro= te: > > > > 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 w= rote: > > > > > > > > On Sat, Jul 19, 2025, at 12:09, Claude Pache wrote: > > > > > > > > Le 19 juil. 2025 =C3=A0 09:46, Rob Landers a =C3=A9= crit : > > > > > > > > On Sat, Jul 19, 2025, at 03:04, Claude Pache wrote: > > > > > > > > > > Le 19 juil. 2025 =C3=A0 00:41, Rob Landers a =C3=A9= crit : > > > > > > The original author (Nikita) suggested that there's nothing in the orig= inal 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, > > > > It is indeed entirely reasonable to have both readonly properties and h= ooked properties (aka accessors), and today PHP has indeed both of them (an= d even asymmetric visibility on top of that, as a separate feature contrari= ly 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 curr= ently disputed. =E2=80=94 What do the other languages allow? Is it possible= to define a readonly property with a user-defined getter? (Disclaimer: Eve= n if one of them allows such a thing, I=E2=80=99ll still think that it is a= bad idea.) > > I do not believe I was cherry picking; I share Claude's interpretation > here that the RFC says that readonly doesn't prevent the language from > 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 conclusions = (it is in the rationale section, after all) and shouldn't be taken as liter= al. Here are some snippets that I think should be looked at a little more c= losely, that seem to align with the vision of the feature, in the context o= f 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 should,= in fact, be considered readonly. 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". 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*. > "Support for first-class readonly properties allows you to directly expos= e public readonly properties, without fear that class invariants could be b= roken through external modification" -- indicates to me that interior mutab= ility (and maybe nondeterminism) should be at the discretion of the interio= r, not the exterior contract. 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. > "...readonly properties do not preclude interior mutability. Objects (or = resources) stored in readonly properties may still be modified internally" = -- further specifies that interior mutability is allowed; only exterior mut= ability isn't. > > It doesn't define "interior mutability" stringently, so we can have diffe= ring opinions on that; but it seems to be "the value inside the property" w= hich may or may not be an object, resource, or a hook's result. 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. 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. > > > > =E2=80=94Claude > > > > > > Hey Claude, > > > > From what I've seen in other languages, this combination is fairly comm= on and not inherently poblematic. > > > > - C# allows get hooks with user-defined logic, even in readonly structs= . > > - Kotlin uses val with a get() body, which is readonly from the consume= r'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 long as = only get hooks are present. However, I'm ok with saying that get hooks may = themselves be readonly which accomplishes the same thing and is easier to r= eason about. 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. 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. > > > > > > 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 "read on= ly" from a public contract point-of-view, we just can't mark it as a readon= ly class. > > In this most recent email, you said: > > > The error you get when trying to modify the "get-only accessor" is `Err= or: Cannot assign to 'name' because it is a read-only property` > > > > Thus, readonly is implied by the get-only accessor, there's no need 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 write > to it? > > > That's the definition in the RFC, from my reading of it: the ability to e= xpose bare properties without worrying that someone else will modify it. On= e might argue that there really isn't a reason for readonly anymore, since = we have aviz + hooks. Or rather, that readonly could be just a shorthand fo= r that -- plus the ability to write to those properties exactly-once. I sus= pect this is where "write-once" is coming from elsewhere in the thread, sin= ce that is the only difference between "manual readonly" and "engine-powere= d readonly". 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. 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. > (including a snippet from an earlier email) > > > I think an init hook is out of the question for 8.5, so I'm not even su= re 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 the > 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 basically= just be a constructor / default value outside of the constructor that spec= ifies an instance-level constant. The readonly property RFC goes into some = details here: > > "As the default value counts as an initializing assignment, a readonly pr= operty 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 ti= me, depending on how exactly property initialization would work in that cas= e, having a default value on a readonly property could preclude userland se= rialization libraries from working, as they would not be able to replace th= e default-constructed object. Whether or not this is a concern depends on w= hether the property is initialized at time of object creation, or as an imp= licit part of the constructor (or similar). As these are open questions, th= e conservative choice is to forbid default values 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 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. I believe that an init hook would solve this problem instead, and maintain the invariant of immutability for readonly properties. > When would the init hook get called? And in what order? This brings back = some memories of solving some Java language bugs where static variables wou= ldn't be initialized in time (they're non-deterministic) causing strange cr= ashes, or the potential to "deadlock" yourself and you need to read propert= ies in a specific order in order to ensure the object gets initialized corr= ectly. 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. > This isn't something that can be solved in a few weeks. Someone(s) needs = to sit down and think through all the possibilities and then create a defin= ition of an init hook that is 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.