Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128111 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 9E72F1A00BC for ; Fri, 18 Jul 2025 14:25:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752848640; bh=FC03+hIwDiYzbRRc+s7371TZZim+Ykeftlfdb0PlX+w=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=XaeSGqOW5vs6mJTLyNBIsPOYPVsRZcuoyyZCYrFmzjyjJ/Rz7xmVio6njhNs+WpEN hqCuM3VQ3V/itIu9yZz3DrP7nZzsVFJWXrbJtl+xoTrllJJC/bQT23+HD3lR+zWjaM j3MYQXUFkno/OjwrX4iBsmEDXYSTnEeaE/dOmQTMN+DI6e+SN6rPwSlAmcvVQVW+nJ go1O1vPLzJAIV3bnsEVjtT4dKYkbg83cQxtE+uDj6TVtHcyDqNRmPRxqFvaHVuNTAy CZJ81iyf0kYh3iNpYfHvTxQ1QXM5e4Q7c0afJiX6La8/3qckyKcuuFHkYaXSG+av+x QEcDXJMHfBAIA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 8B141180083 for ; Fri, 18 Jul 2025 14:23:58 +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=0.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_40, DKIM_INVALID,DKIM_SIGNED,DMARC_MISSING,HTML_MESSAGE,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,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 sender4-of-o54.zoho.com (sender4-of-o54.zoho.com [136.143.188.54]) (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 14:23:57 +0000 (UTC) ARC-Seal: i=1; a=rsa-sha256; t=1752848737; cv=none; d=zohomail.com; s=zohoarc; b=DN2zbkucc90Nz1+ankNZRWGt1mEsnpoOQ3TnJrXNFx2ixlz1fjBDdRFtAkKVPcbCWfiRufhfK9KiFcej2eIQxYwgftG8BbVnHQr75q8t3ap014vt1A1f1kX4tTYfDeMK0QnpYHhuoYFbn/xugRWWTbHZRlghORJA5BBOEuxARd8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1752848737; h=Content-Type:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=dwalBlrhA2lO2CvZHtpVXVkU3mpCUL9MIzYvDlq/wiQ=; b=MpiO08XeByvDACSyaN99VrKUyPlNfDCUmKN7VHvPYfIfJpJ4rNtURMcgbYLCw5oTr2mQOQ0G5q1brlgE+stdd85eKBJmp2tWXpgvXPF7VogcT//mAqOp2B4JVA5BciNYvqjcywKoFRoId1FoO0koQ6I03PHthrrd5WPVvcFKvGs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=faizanakram.me; spf=pass smtp.mailfrom=hello@faizanakram.me; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1752848737; s=zoho; d=faizanakram.me; i=hello@faizanakram.me; h=MIME-Version:References:In-Reply-To:From:From:Date:Date:Message-ID:Subject:Subject:To:To:Cc:Cc:Content-Type:Message-Id:Reply-To; bh=dwalBlrhA2lO2CvZHtpVXVkU3mpCUL9MIzYvDlq/wiQ=; b=J3G+YkRlmAFP0l+TvK40CUkbdNOWMZ9w/mOPIuq2ns+WO+1d4h7RigXzxLcsqZO3 QBTDFwJ1OQrflqAkWII6I+SsQ/TLL7pUC9OldmUtyARHW6Jt33X3rPTvUnNYTaWM5t8 0OuOhAl+9mPecYeQXMHClfHYjxQRTBk1Dyw13GD0= Received: by mx.zohomail.com with SMTPS id 1752848735707283.66424250738; Fri, 18 Jul 2025 07:25:35 -0700 (PDT) Received: by mail-yw1-f169.google.com with SMTP id 00721157ae682-7170344c100so19169447b3.0 for ; Fri, 18 Jul 2025 07:25:35 -0700 (PDT) X-Forwarded-Encrypted: i=1; AJvYcCVp9GhcbnKpeMjDWmamJpmz+T3yPb0c19WbJ2HJ/6ygwIuiRPgqKNTauPUv6Qkg0jLixAimIayj9gI=@lists.php.net X-Gm-Message-State: AOJu0YwmXYvlpNJ6jmdZDcqL81uQwnuWKeVBTFhogxMipfhZ0QLhJ83z 5OSdNDG3wka76TZs9QCIEsoqNEFNDqds8ZLvfO8NoC1aEFpik/rfh3W2BYGAp7LVbFLQm1T544t TLUIar2gaWJY1jT8P/ONfgZjvr57PD1U= X-Google-Smtp-Source: AGHT+IFgMQOs8re84BMyg0Z/v0hlvo0XtR8co3W7997v8n736XH2dwS+P77Dvlsglhj+GEFN/XkH9zmlqB1Lffx6MNg= X-Received: by 2002:a05:690c:22c6:b0:709:197d:5d3c with SMTP id 00721157ae682-71837124a08mr111961707b3.11.1752848734922; Fri, 18 Jul 2025 07:25:34 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 References: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> <46857A6D-5EAF-44AF-A2DE-9B40AF8DE8C8@gmail.com> <41241c3d-a601-4fb8-9f32-976bea3b660e@app.fastmail.com> <308e51e9-ebc9-4ab1-a727-46854b27701f@bastelstu.be> In-Reply-To: <308e51e9-ebc9-4ab1-a727-46854b27701f@bastelstu.be> Date: Fri, 18 Jul 2025 16:25:23 +0200 X-Gmail-Original-Message-ID: X-Gm-Features: Ac12FXwPtrl1u4rAqlVIBXyE4t6FfmtgSU-jTJ6uEYs4R9bdilXlYrFsOWAbc2M Message-ID: Subject: Re: [PHP-DEV] [RFC] Readonly property hooks To: =?UTF-8?Q?Tim_D=C3=BCsterhus?= Cc: Larry Garfield , php internals Content-Type: multipart/alternative; boundary="00000000000030b056063a34e694" X-ZohoMailClient: External From: hello@faizanakram.me (Faizan Akram Dar) --00000000000030b056063a34e694 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, 18 Jul 2025, 15:16 Tim D=C3=BCsterhus, wrote: > Hi > > On 7/9/25 16:05, Larry Garfield wrote: > > 1. `readonly` bills itself as immutability, but it fundamentally is > not. There are at least two loopholes: __get and a mutable object saved = to > a property. So while it offering immutability guarantees is nice in > theory, it's simply not true in practice. `readonly` has always been > misnamed; it should really be `writeonce`, because that's all it is. (On= ce > again, this is likely the most poorly designed feature we've added in man= y > years.) > > No, readonly is readonly, not writeonce. Stop trying to redefine > readonly as writeonce to justify bad design decisions. > > Readonly guarantees that once I successfully read from a property that > I'll get the same thing out on subsequent reads and I consider this to > be valuable and strongly disagree on the "most poorly designed feature" > bit. > > Yes, I understand that __get() currently is an exception to that > guarantee, but that does not mean that further exceptions should be > added to water down readonly into something that is completely useless. > > > 2. In 8.4, if a class is marked `readonly`, you basically forbid it fro= m > having any hooks of any kind, even though you absolutely can honor the > write-once-ness of the properties while still having hooks. And that > applies to child classes, too, because `readonly`-ness inherits. So addi= ng > a single hook means you have to move the readonly to all the other > properties individually, which if inheritance is involved you cannot do. > > > > The RFC aims to address point 2 in a way that still respects point 1, > but only point 1 as it actually is (write-once), not as we wish it to be > (immutability). > > Readonly is immutability of values (or in other words immutability of > identity). For objects this means immutability of the object handle, for > other types this means actual immutability. > > I also feel compelled to mention at this point that the commonly > repeated statement of "Objects are passed by reference" is incorrect. > It's that "the object handle is passed by value". And then it's fully > consistent with how readonly works as of now. > > > * set hooks for validation, which don't impact writeonce-ness. I think > everyone seems on board with that. > > Yes, allowing set hooks for readonly properties seems sound to me. > > > * Lazy computed properties. I use these a ton, even for internal > caching purposes. 99% of the time I cache them because my objects are > practically immutable, and $this->foo ??=3D whatever is an easy enough > pattern. (If they're not cached then it would be a virtual property, whi= ch > we're not dealing with for now.) As long as you're caching it in that > fashion, the write-once-ness still ends up respected. > > > > Honestly, Nick tried to come up with examples yesterday while we were > talking that would not fit into one of those two categories, and for ever= y > one of them my answer was "if your code is already that badly designed, > there's nothing we can do for you." :-) > > It's nice to hear that there are no other usecases for hooks on readonly > properties, since this means that we can just allow the 'set' hook and > add an 'init' hook for the lazy computation use-case without needing to > violate the semantics of `readonly` by allowing a `get` hook. > > > An init hook would be clearer, certainly, though it also has its own > edge cases. Can you set something that has an init hook? What happens i= f > there's both a get and init hook? These probably have answers that could > be sorted out, but that's a different question from "why the > does a readonly class forbid me using even rudimentary hooks???" > > Not clearer. It would be the only thing that is semantically sound. > While it certainly needs careful consideration of semantics to ensure > there are no edge cases, figuring this out should be much easier than > intentionally introducing edge cases via a get hook. > > As to your questions: The init hook is triggered when reading from a > property that is in the uninitialized state. The return value of the > hook is stored in the property and returned as the result of the read > operation. Having an init hook implies the property is non-virtual. > > - Yes, you can set something that has an init hook. Setting means that > the property will no longer be uninitialized, which means that the init > hook will no longer be called. > - If there is both a get and an init hook, the init hook will be called > when the backing store is uninitialized. The result of the init hook > will then also go through the get hook. On subsequent reads only the get > hook will be called. > > Best regards > Tim D=C3=BCsterhus > Hi Tim, The problem with allowing only set hooks is that readonly class won't be compatible with hooks, I think that is one of the main motivations behind this RFC. Faizan Akram Dar faizanakram.me --00000000000030b056063a34e694 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
<= div dir=3D"ltr">

On Fri, 18 Jul 2025, 15:16 Tim D=C3=BCsterhus, <tim@bastelstu.be> wrote:
Hi

On 7/9/25 16:05, Larry Garfield wrote:
> 1. `readonly` bills itself as immutability, but it fundamentally is no= t.=C2=A0 There are at least two loopholes: __get and a mutable object saved= to a property.=C2=A0 So while it offering immutability guarantees is nice = in theory, it's simply not true in practice.=C2=A0 `readonly` has alway= s been misnamed; it should really be `writeonce`, because that's all it= is.=C2=A0 (Once again, this is likely the most poorly designed feature we&= #39;ve added in many years.)

No, readonly is readonly, not writeonce. Stop trying to redefine
readonly as writeonce to justify bad design decisions.

Readonly guarantees that once I successfully read from a property that
I'll get the same thing out on subsequent reads and I consider this to =
be valuable and strongly disagree on the "most poorly designed feature= " bit.

Yes, I understand that __get() currently is an exception to that
guarantee, but that does not mean that further exceptions should be
added to water down readonly into something that is completely useless.

> 2. In 8.4, if a class is marked `readonly`, you basically forbid it fr= om having any hooks of any kind, even though you absolutely can honor the w= rite-once-ness of the properties while still having hooks.=C2=A0 And that a= pplies to child classes, too, because `readonly`-ness inherits.=C2=A0 So ad= ding a single hook means you have to move the readonly to all the other pro= perties individually, which if inheritance is involved you cannot do.
>
> The RFC aims to address point 2 in a way that still respects point 1, = but only point 1 as it actually is (write-once), not as we wish it to be (i= mmutability).

Readonly is immutability of values (or in other words immutability of
identity). For objects this means immutability of the object handle, for other types this means actual immutability.

I also feel compelled to mention at this point that the commonly
repeated statement of "Objects are passed by reference" is incorr= ect.
It's that "the object handle is passed by value". And then it= 's fully
consistent with how readonly works as of now.

> * set hooks for validation, which don't impact writeonce-ness.=C2= =A0 I think everyone seems on board with that.

Yes, allowing set hooks for readonly properties seems sound to me.

> * Lazy computed properties.=C2=A0 I use these a ton, even for internal= caching purposes.=C2=A0 99% of the time I cache them because my objects ar= e practically immutable, and $this->foo ??=3D whatever is an easy enough= pattern.=C2=A0 (If they're not cached then it would be a virtual prope= rty, which we're not dealing with for now.)=C2=A0 As long as you're= caching it in that fashion, the write-once-ness still ends up respected. >
> Honestly, Nick tried to come up with examples yesterday while we were = talking that would not fit into one of those two categories, and for every = one of them my answer was "if your code is already that badly designed= , there's nothing we can do for you." :-)

It's nice to hear that there are no other usecases for hooks on readonl= y
properties, since this means that we can just allow the 'set' hook = and
add an 'init' hook for the lazy computation use-case without needin= g to
violate the semantics of `readonly` by allowing a `get` hook.

> An init hook would be clearer, certainly, though it also has its own e= dge cases.=C2=A0 Can you set something that has an init hook?=C2=A0 What ha= ppens if there's both a get and init hook?=C2=A0 These probably have an= swers that could be sorted out, but that's a different question from &q= uot;why the <censored> does a readonly class forbid me using even rud= imentary hooks???"

Not clearer. It would be the only thing that is semantically sound.
While it certainly needs careful consideration of semantics to ensure
there are no edge cases, figuring this out should be much easier than
intentionally introducing edge cases via a get hook.

As to your questions: The init hook is triggered when reading from a
property that is in the uninitialized state. The return value of the
hook is stored in the property and returned as the result of the read
operation. Having an init hook implies the property is non-virtual.

- Yes, you can set something that has an init hook. Setting means that
the property will no longer be uninitialized, which means that the init hook will no longer be called.
- If there is both a get and an init hook, the init hook will be called when the backing store is uninitialized. The result of the init hook
will then also go through the get hook. On subsequent reads only the get hook will be called.

Best regards
Tim D=C3=BCsterhus

=

Hi Tim,

The problem with allowing only set hooks is t= hat readonly class won't be compatible with hooks, I think that is one = of the main motivations behind this RFC.

<= div dir=3D"auto">=C2=A0

= =C2=A0=C2=A0
Faizan Akram Dar

--00000000000030b056063a34e694--