Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128104 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 6E4BF1A00BC for ; Fri, 18 Jul 2025 12:32:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752841872; bh=ik0q8vhenWxsXkGgmhksvRfdcMS52RnFnZhuSzt+aqc=; h=Date:Subject:To:References:From:In-Reply-To:From; b=ihOtZdLI7d5h5drKevPXGgSrp4eDMTLEUI/bLnsQP5V94y8sJoJkA22T57GBUvW7L ywnHxT4W5xglBDGS7BlCfWznMAr/Qf8ihjvDl6c4trS4dQOl5mistJ4bqQB5Oo7fnO jugP7U7Mp1oWpfjReiGXVq9frFeR0DONHw3xhlzcmQ3CJ2AT3kq4QvtO32MqvYY2NG amClR+lCfEENFC/LCsx4xXx9PEgNcdrM0EsAA+TEBEcvg0O/Ir5nLSNNacfe8DvMRm FPh5/tVg6nwrmzgtBWI6G2ewMSVrqRE89QohLgA8QNwaqD8g/Z/29rHNlOi0tg4PAi 0IEgM/fBWf86w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id C7B8D1804D9 for ; Fri, 18 Jul 2025 12:31:11 +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,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 chrono.xqk7.com (chrono.xqk7.com [176.9.45.72]) (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 12:31:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bastelstu.be; s=mail20171119; t=1752841976; bh=ZfYX88w7N4DnxpUi2wa5rOK6ALuKg/1smHdlz6PE/Fg=; h=Message-ID:Date:MIME-Version:Subject:To:References:From: In-Reply-To:Content-Type:from:to:cc:subject:message-id; b=J4azT9n7brbW5EFf0d8W2X5ngM9UxzZDPObGbisvUMX10p3PuDaRItGZFRQS7JNZY N2DN2gly2UKFjgsjXQF1uRFvrnBEmr9yMKyq+FLUr0tjGpbNk1ajo9cAXw6a3lOCFI BlBXwsTbPK18JoYdbpQYxw7zE+wyQOMICmp7R7NxmeEo6ZsobitQNuaUbQ5EWSSqFQ qFfcv8Xifc70gASp4u44eHDeU/BFeBjUIsWOAY79d276aF7j86+NgtuKEW5SopoDmZ Groy318XKT8RbZ8JXNQoo82diryFR9SDY0zAM7oPn/cJ6Bz8vdvyfX0Bp69zSuHGcF YU6vT70wbN24Q== Message-ID: <308e51e9-ebc9-4ab1-a727-46854b27701f@bastelstu.be> Date: Fri, 18 Jul 2025 14:32:55 +0200 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 Subject: Re: [PHP-DEV] [RFC] Readonly property hooks To: Larry Garfield , php internals References: <1e8634d7-ac1a-4025-b4e2-1948aabf5251@app.fastmail.com> <46857A6D-5EAF-44AF-A2DE-9B40AF8DE8C8@gmail.com> <41241c3d-a601-4fb8-9f32-976bea3b660e@app.fastmail.com> Content-Language: en-US In-Reply-To: <41241c3d-a601-4fb8-9f32-976bea3b660e@app.fastmail.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit From: tim@bastelstu.be (=?UTF-8?Q?Tim_D=C3=BCsterhus?=) 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. (Once again, this is likely the most poorly designed feature we'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 from 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 adding 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 ??= whatever is an easy enough pattern. (If they're not cached then it would be a virtual property, which 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 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 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 if 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üsterhus