Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128120 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 0675F1A00BC for ; Fri, 18 Jul 2025 17:09:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752858441; bh=IcJa3xYWtf7WHb2UVGbiFbsdtZnPg9WRxTmNnnk7lE8=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=V7l29jrqASpw0ZlorcQXUx4uB5xSIdA/VUL6IbJHVM4HeJsIKilPQnscO/v0UB/62 yIiutBURKWOWi2rcSj+isyaCjhUUT0G4KEjPPXzj7HcCOVDIjR0khm/+tZgZYcLVLC pBCo9z5SxnjJTwf1v8AJCb8PYF2RMADu/ukegdxHfQ/ZLMamLoX+EJGt7icdOfA+9u Ljt0u2jZJ9/TZtqQ6i8K2R2kcsWuY6HNpMzPuJzAoAEtSbarSPbkc0tAM3I8r9PDVw p1CekwwRAVSFeWuTWb7YfYGf05TxnmEtAYn5Nn1M3JsLkRh970EN1Ma/LsEEX3a+Hu WlKin72qFc78Q== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 4148B1805DB for ; Fri, 18 Jul 2025 17:07:17 +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,HTML_MESSAGE, 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 avril.gn2.hosting (avril.gn2.hosting [84.19.162.247]) (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 ; Fri, 18 Jul 2025 17:07:13 +0000 (UTC) Received: from avril.gn2.hosting (localhost [127.0.0.1]) by avril.gn2.hosting (Postfix) with ESMTP id 386DF1C40C7A; Fri, 18 Jul 2025 19:08:57 +0200 (CEST) Received: from smtpclient.apple (unknown [202.46.151.141]) by avril.gn2.hosting (Postfix) with ESMTPSA id C411C1C409DD; Fri, 18 Jul 2025 19:08:54 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nicksdot.dev; s=default; t=1752858536; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=zuhAbgMdTc+OzWhFDxeM/x6Z6uDKFmrhVk4nXpRDP5w=; b=TYg3KRQVMrikO6bXmCLqbQAyuE/GoReKRL7l4XcLfTQxAIHhebUluoYP5Re+F9zMixhAk+ TaFLpr1jLW5kdT5H+KzsIcbMF5j9KLjW9cvleWd9GNH3gImF4QJN726iF+fVbN1CVNkgXq rO9dm4ydQYPMKnf1mNeVPuWO1O6DewAI62AYe4jZA5IGuUIf/0kn8ohBe2n6DW89Z74ciD PSVFFUkqZ1aWz+v3U6OMq0BY02HX5dQbtHcy7MOcjIOdVaWUDc5wVKO+bwsPkxmpOieCMv 0J28OSvaDtj/fCDUn4MrNqL+9GjnMC+XnUhjqXKX/mT8WlJAIJdRi8GWqI59cQ== Message-ID: Content-Type: multipart/alternative; boundary="Apple-Mail=_BAFF1159-093F-4698-8928-9E75A4447AE3" Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.600.51.1.1\)) Subject: Re: [PHP-DEV] [RFC] Readonly property hooks Date: Sat, 19 Jul 2025 00:08:37 +0700 In-Reply-To: Cc: Rob Landers , =?utf-8?Q?Tim_D=C3=BCsterhus?= , hello@faizanakram.me, Larry Garfield , php internals , ocramius@gmail.com, tovilo.ilija@gmail.com To: erictnorris@gmail.com References: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> <9D5043B2-1589-4FD5-B289-6E98FB1177BE@nicksdot.dev> <0856c89f-2000-448a-bbbf-c145a8699f6a@app.fastmail.com> <2641b8bc-6337-4b75-a5a3-93dee1b03796@bastelstu.be> <0d9a2968-26dd-43dd-9733-d2eb621ab9ec@app.fastmail.com> X-Mailer: Apple Mail (2.3826.600.51.1.1) From: php@nicksdot.dev (Nick) --Apple-Mail=_BAFF1159-093F-4698-8928-9E75A4447AE3 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On 18. Jul 2025, at 23:48, Eric Norris = wrote: >=20 > On Fri, Jul 18, 2025 at 12:01=E2=80=AFPM Rob Landers = > wrote: >>=20 >>=20 >>=20 >> On Fri, Jul 18, 2025, at 17:25, Tim D=C3=BCsterhus wrote: >>=20 >> Hi >>=20 >> On 7/14/25 15:38, Larry Garfield wrote: >>> Thanks, Ilija. You expressed my concerns as well. And yes, in = practice, readonly classes over-reaching is the main use case; if you're = marking individual properties readonly, then just don't mark the one = that has a hook on it (use aviz if needed) and there's no issue. >>=20 >> A readonly class is not just a convenience shortcut to mark each >> individual property as readonly. It has important semantics of its = own, >> because it forces child classes to also be readonly. And even for = final >> classes it communicates to the user that "I won't be adding = non-readonly >> properties to the class". >>=20 >>=20 >> Wasn=E2=80=99t that the entire point of readonly classes? Because it = was painful to write readonly for every property. Then if a property is = readonly, the inherited property is also readonly, so, by extension: a = class extending a readonly class is also readonly. >>=20 >> There=E2=80=99s no =E2=80=9Ccommunication=E2=80=9D here; just logic. >>=20 >>=20 >> Marking a class as readonly must therefore be a deliberate decision, >> since it affects the public API of your class and in turn also user >> expectations. >>=20 >>=20 >> Not really. I can remove the readonly designation and manually mark = every property as readonly. The behavior of the class doesn=E2=80=99t = magically change. Or, at least, I hope it doesn=E2=80=99t. >>=20 >>=20 >>> Perhaps we're thinking about this the wrong way, though? So far = we've talked as though readonly makes the property write-once. But... = what if we think of it as applying to the field, aka the backing value? >>=20 >> I think of readonly from the view of the public API surface of an >> object. The property hooks RFC was very explicit in that property = hooks >> are intended to be =E2=80=9Ctransparent to the user=E2=80=9D and can = be added without >> breaking the public API. In other words: Whether or not a property is >> implemented using a hook should be considered an implementation = detail >> and as a user of a class I do not care whether there is a backing = value >> or not. >>=20 >>> So readonly doesn't limit calling the get hook, or even the set = hook, multiple times. Only writing to the actual value in the object = table. That gives the exact same set of guarantees that a getX()/setX() = method would give. The methods can be called any number of times, but = the stored value can only be written once. >>=20 >> As a user of a class the "backing table" is mostly inaccessible to me >> when interacting with objects. It's only exposed via var_dump() and >> serialize(), the former of which is a debug functionality and the = output >> of latter not something I must touch. >>=20 >>> It would not guarantee $foo->bar =3D=3D=3D $foo->bar in all cases = (though that would likely hold in the 99% case in practice), but then, = $foo->getBar() =3D=3D=3D $foo->getBar() has never been guaranteed = either. >>=20 >> Properties and methods are something different. For methods there a >> reasonable expectation that *behavior* is associated with them, for >> properties there is not. >>=20 >>=20 >> Unless I missed something. Hooks are fancy methods? There is nothing = intrinsic about object properties. There is nothing that says two calls = to the same property=E2=80=99s getters are going to result in the same = values. There is asynchronous php, declare ticks, etc. especially in the = case of globals, there is no guarantee you even have the same object. At = the end of the day, it is up to the programmer building that system / = program to provide those guarantees=E2=80=94 not the language. >=20 > I do think that, without any additional information, it would be > reasonable to assume that `$foo->bar =3D=3D=3D $foo->bar`, i.e. there = would > not be side-effects until you've called a method or written to the > object in some way. So I share Tim's opinion here, but I do agree that > with hooks available this is not actually a guarantee. You could > certainly have a `$foo->random_value` property and document that it > will be different each time you call it. >=20 > That said, once the programmer has added the readonly designation to a > property, I do think that something says that two calls to the same > property will result in the same values - the readonly designation. I > disagree with the point that it's not up to the language - the > language should provide an affordance for enforcing programmer intent, > and I see no reason to even have a readonly designation if we're going > to make it easily circumventable or otherwise just a "hint". >=20 > It seems that one common counterpoint to the "let's not make it > circumventable" argument is to point out that it's already > circumventable via __get. I agree with Claude that this is not a > justification for making it *easier* to circumvent. I would also like > to note that the original RFC > (https://wiki.php.net/rfc/readonly_properties_v2#unset) seems to allow > this behavior *for the purpose of lazy initialization*. With an `init` > hook, we'd have solved this problem, and could deprecate the `__get` > hack for `readonly` properties / classes. >=20 > Nicolas Grekas said "__get is certainly not legacy; removing it would > break many use cases without proper alternatives.", but note that I'm > only suggesting we could maybe deprecate __get for `readonly` > properties once we had an `init` hook - I'm not proposing deprecating > it generally. Without a counterexample, I don't think there would be > another reason for `__get` to work with `readonly` properties. Hey all, I allow myself to answer in one single mail, instead to all of you = individually. Honestly, I didn=E2=80=99t expect that this RFC will be THAT = controversial. =F0=9F=98=85 However, I get it. There are good arguments on either side.=20 I did hope that the =E2=80=9Cimplicit cache=E2=80=9D is a decent middle = ground, but that also didn=E2=80=99t work out as I thought. As mentioned earlier, this is my very first RFC. I am at a point where I = am a bit overwhelmed. That said, Larry and I heard you and already decided to offer a split = vote to enable us to at least land =E2=80=9Cset only=E2=80=9D in 8.5.=20 If we didn=E2=80=99t misunderstood it, then y=E2=80=99all agreed on = `set` (only) should be allowed? This would IMHO already be a huge improvement compared to now; and a low = hanging fruit.=20 Not exactly what I wanted, but it is what it is. Long story short. We simply don=E2=80=99t have the time to get `init` = sorted before feature freeze.=20 I offer to follow up with a =E2=80=9Creadonly `init` hook=E2=80=9D RFC = for 8.6 to sort the rest. I=E2=80=99d appreciate if voters could settle on a yes for =E2=80=9Cset = only=E2=80=9D for 8.5. Wdyt? Would this help to get closer to closing the discussion? Cheers, Nick --Apple-Mail=_BAFF1159-093F-4698-8928-9E75A4447AE3 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8
On 18. Jul = 2025, at 23:48, Eric Norris <eric.t.norris@gmail.com> = wrote:

On Fri, Jul 18, 2025 at = 12:01=E2=80=AFPM Rob Landers <rob@bottled.codes> = wrote:



On Fri, Jul 18, 2025, at 17:25, Tim = D=C3=BCsterhus wrote:

Hi

On 7/14/25 15:38, Larry Garfield = wrote:
Thanks, Ilija.  You expressed = my concerns as well.  And yes, in practice, readonly classes = over-reaching is the main use case; if you're marking individual = properties readonly, then just don't mark the one that has a hook on it = (use aviz if needed) and there's no issue.

A = readonly class is not just a convenience shortcut to mark = each
individual property as readonly. It has important semantics of = its own,
because it forces child classes to also be readonly. And = even for final
classes it communicates to the user that "I won't be = adding non-readonly
properties to the class".


Wasn=E2=80=99t= that the entire point of readonly classes? Because it was painful to = write readonly for every property. Then if a property is readonly, the = inherited property is also readonly, so, by extension: a class extending = a readonly class is also readonly.

There=E2=80=99s no = =E2=80=9Ccommunication=E2=80=9D here; just logic.


Marking a = class as readonly must therefore be a deliberate decision,
since it = affects the public API of your class and in turn also = user
expectations.


Not really. I can remove the readonly = designation and manually mark every property as readonly. The behavior = of the class doesn=E2=80=99t magically change. Or, at least, I hope it = doesn=E2=80=99t.


Perhaps we're = thinking about this the wrong way, though?  So far we've talked as = though readonly makes the property write-once.  But... what if we = think of it as applying to the field, aka the backing = value?

I think of readonly from the view of the = public API surface of an
object. The property hooks RFC was very = explicit in that property hooks
are intended to be =E2=80=9Ctransparent= to the user=E2=80=9D and can be added without
breaking the public = API. In other words: Whether or not a property is
implemented using a = hook should be considered an implementation detail
and as a user of a = class I do not care whether there is a backing value
or = not.

So readonly doesn't limit calling = the get hook, or even the set hook, multiple times.  Only writing = to the actual value in the object table.  That gives the exact same = set of guarantees that a getX()/setX() method would give.  The = methods can be called any number of times, but the stored value can only = be written once.

As a user of a class the "backing = table" is mostly inaccessible to me
when interacting with objects. = It's only exposed via var_dump() and
serialize(), the former of which = is a debug functionality and the output
of latter not something I = must touch.

It would not guarantee = $foo->bar =3D=3D=3D $foo->bar in all cases (though that would = likely hold in the 99% case in practice), but then, $foo->getBar() = =3D=3D=3D $foo->getBar() has never been guaranteed = either.

Properties and methods are something = different. For methods there a
reasonable expectation that *behavior* = is associated with them, for
properties there is = not.


Unless I missed something. Hooks are fancy methods? = There is nothing intrinsic about object properties. There is nothing = that says two calls to the same property=E2=80=99s getters are going to = result in the same values. There is asynchronous php, declare ticks, = etc. especially in the case of globals, there is no guarantee you even = have the same object. At the end of the day, it is up to the programmer = building that system / program to provide those guarantees=E2=80=94 not = the language.

I do = think that, without any additional information, it would be
reasonable to assume that `$foo->bar =3D=3D= =3D $foo->bar`, i.e. there would
not be = side-effects until you've called a method or written to the
object in some way. So I share Tim's = opinion here, but I do agree that
with = hooks available this is not actually a guarantee. You could
certainly have a `$foo->random_value` = property and document that it
will be = different each time you call it.

That = said, once the programmer has added the readonly designation to = a
property, I do think = that something says that two calls to the same
property will result in the same values - = the readonly designation. I
disagree with the point that it's not up to the language - = the
language should provide = an affordance for enforcing programmer intent,
and I see no reason to even have a readonly = designation if we're going
to make = it easily circumventable or otherwise just a "hint".

It seems that one common counterpoint to = the "let's not make it
circumventable" argument is to point out that it's = already
circumventable via = __get. I agree with Claude that this is not a
justification for making it *easier* to = circumvent. I would also like
to note = that the original RFC
(https://wiki.php.net/rfc/readonly_properties_v2#unset) seems to allow
this behavior *for the purpose of lazy = initialization*. With an `init`
hook, = we'd have solved this problem, and could deprecate the `__get`
hack for `readonly` properties / = classes.

Nicolas Grekas said = "__get is certainly not legacy; removing it would
break many use cases without proper = alternatives.", but note that I'm
only = suggesting we could maybe deprecate __get for `readonly`
properties once we had an `init` hook - I'm = not proposing deprecating
it = generally. Without a counterexample, I don't think there would = be
another reason for = `__get` to work with `readonly` = properties.

Hey all,


I allow myself to answer in one single = mail, instead to all of you individually.


Honestly, I didn=E2=80=99t expect that = this RFC will be THAT controversial. =F0=9F=98=85

However, I get it. There are good = arguments on either side. 


I did hope that the =E2=80=9Cimplicit = cache=E2=80=9D is a decent middle ground, but that also didn=E2=80=99t = work out as I thought.


As mentioned earlier, this is my very = first RFC. I am at a point where I am a bit overwhelmed.


That said, Larry and I heard you and = already decided to offer a split vote to enable us to at least = land =E2=80=9Cset only=E2=80=9D in 8.5. 

If we didn=E2=80=99t misunderstood it, = then y=E2=80=99all agreed on `set` (only) should be allowed?


This would IMHO already be a huge = improvement compared to now; and a low hanging fruit. 

Not exactly what I wanted, but it is = what it is.

Long story = short. We simply don=E2=80=99t have the time to get `init` sorted before = feature freeze. 


I offer to follow up with a = =E2=80=9Creadonly `init` hook=E2=80=9D RFC for 8.6 to sort the = rest.


I=E2=80=99d appreciate if voters could = settle on a yes for =E2=80=9Cset only=E2=80=9D for 8.5.


Wdyt? Would this help to get closer to = closing the discussion?


Cheers,

Nick


= --Apple-Mail=_BAFF1159-093F-4698-8928-9E75A4447AE3--