Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:122660 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 qa.php.net (Postfix) with ESMTPS id B1BF91AD8F6 for ; Sat, 16 Mar 2024 17:51:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1710611535; bh=vlLXSnwNyG1jsDTjyo0mcOLEdo506TIUkZ6zdFc3aJ4=; h=References:In-Reply-To:From:Date:Subject:To:From; b=maowCqRQFVXtfua2bat22lzjYo1BH69CnGhtrnxy118l5R5eIWVA4n5lbtYXgzwKn 0bJoqpHtuSrljoDfSBx5erDWk1hq7sFUEgpv2DGi7TgLq8Y0G4cLL63kjo//beu97T iaKY8iAmZe5YadcCuvbWgyicoeiEZQz0yn3mk7BeAiM63/PJd0GLf7t6w0XwnpotnJ HUp+2b+oAWB0hnKCq4vBXbY/F8JQZOS5yK5WlyUsVEpXyLeTHS5Hsqst9mTmrHfDsd NvCztUiYPYcxRlRCORCCgZNDTmlXvVD5CE80yznLVfXVNfFALeAfIn7iG5GWWU4ouM 9QDCctF9WGR4Q== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 2806C180071 for ; Sat, 16 Mar 2024 17:52:15 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS, T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-qv1-f42.google.com (mail-qv1-f42.google.com [209.85.219.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 ; Sat, 16 Mar 2024 17:52:11 +0000 (UTC) Received: by mail-qv1-f42.google.com with SMTP id 6a1803df08f44-69145fc4265so20647996d6.1 for ; Sat, 16 Mar 2024 10:51:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1710611512; x=1711216312; darn=lists.php.net; h=content-transfer-encoding:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=E30W4Wp37VgWiDjWxITrgmDp+B+dq6mDQZjui6XdcjA=; b=clDWrw19mP1Q+OWQk+jPUKqvBALTAmxaboay4EePbL3qmgYI/O+lXrepcyaZnQW5pj x6YUn+J4ISteWfyVm+4LcGC7E2akuhmPZR9TtzKm1YY/PDxLMdt4qiv2RGouUQx8nPH0 pRBtcnvang19PU1Cty5OMUEZBrLHYfJQzGJ+Ap1E+Zpke4rp5EpdVakUSgS3bwgquDSg Ltw5a+8be/10vzOXTY0BpjyoIGBvnLT3kVovL3Dx1/LSG1CxxWTgTcWX7Bytgr9Wzhmu Opdv7N2eNVmABSI78YTYC6iA0QgzhHyPrA5zjfB1qmS11pblCwy9QJy11R11VX598kCW DIJA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1710611512; x=1711216312; h=content-transfer-encoding:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=E30W4Wp37VgWiDjWxITrgmDp+B+dq6mDQZjui6XdcjA=; b=sNMOMdX7vnyrWVtrLqHYGL3IhLa0e7Ucvhp29CNeWV3XESzsI35TYjUNAJS5iUs2DF j5kGWeoMJEcE3A9ZiBwGHqvp9o94I8EYQ85zphMveRb9ETNsM0EvK+vT3tuNsBr/qdft JFDCH2ZXnnAikrrUC5l0k5LYmJL1guFo2mB2S3JPahDFWp7AmoRPWsjo+r+Vz1UBKEEy SfnSUUqUHdEuI5Mm42KyeKVBXATJl2CKPNiR/TFG5SXb7CYydhf4ghsOdaRSK2ryKSP7 kjkJ9I6DRk1/MPMYNnpLTIkfSUeULTnYCj7ZEgdE4Xer/7DxrzKj0kz52Ek3bW0zlu4U JfdQ== X-Gm-Message-State: AOJu0Yzt1mvNiN+oBUcMEgqjlWdxZBRczi7KQJPIrQ03ctiBcB3oDC57 8n1hsNKSKTWo1odC9drkOs09ZJuFhy+jXmbbm+9PogA3SdL3SioXvrN4vH1PUxG4vCS+uIoTQB8 mJcYoJVSH9hQVHpKdm9EXJeGhrR34K1Uim5DDbphh X-Google-Smtp-Source: AGHT+IGliWS3pl3ce0Wjz9pUVpj8INvnnczAxVNpE1r8uiXOBgr0SeZbNyax9zGEXdtELWYFra9eDaatUQs+IlXZbwQ= X-Received: by 2002:a05:6214:b90:b0:690:6ce7:42b2 with SMTP id fe16-20020a0562140b9000b006906ce742b2mr7181270qvb.42.1710611511816; Sat, 16 Mar 2024 10:51:51 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 References: <7eada0fd-39c5-4a89-8c74-80c671801a2d@app.fastmail.com> <1698692e-8eb1-4bfc-a743-375696cd8f1c@rwec.co.uk> <154481e0-5f62-4026-994a-28a644d71527@app.fastmail.com> <46609F15-DD40-4BD2-A78A-16021C3447C8@rwec.co.uk> In-Reply-To: <46609F15-DD40-4BD2-A78A-16021C3447C8@rwec.co.uk> Date: Sat, 16 Mar 2024 18:51:40 +0100 Message-ID: Subject: Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2 To: PHP internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable From: tovilo.ilija@gmail.com (Ilija Tovilo) Hi Rowan On Sat, Mar 16, 2024 at 9:32=E2=80=AFAM Rowan Tommins [IMSoP] wrote: > > On 16 March 2024 00:19:57 GMT, Larry Garfield wr= ote: > > >Well, reading/writing from within a set/get hook is an obvious use case = to support. We cannot do cached properties easily otherwise: > > > >public string $expensive { > > get =3D> $this->expensive ??=3D $this->compute(); > > set { > > if (strlen($value) < 50) throw new Exception(); > > $this->expensive =3D $value; > > } > >} > > > To play devil's advocate, in an implementation with only virtual properti= es, this is still perfectly possible, just one declaration longer: > > private string $_expensive; > public string $expensive { > get =3D> $this->_expensive ??=3D $this->compute(); > set { > if (strlen($value) < 50) throw new Exception(); > $this->_expensive =3D $value; > } > } > > Note that in this version there is an unambiguous way to refer to the raw= value from anywhere else in the class, if you wanted a clearAll() method f= or instance. > > I can't stress enough that this is where a lot of my thinking comes from:= that backed properties are really the special case, not the default. Anyth= ing you can do with a backed property you can do with a virtual one, but th= e opposite will never be true. > > > The minimum version of backed properties is basically just sugar for that= - the property is still essentially virtual, but the language declares the= backing property for you, leading to: > > public string $expensive { > get =3D> $field ??=3D $this->compute(); > set { > if (strlen($value) < 50) throw new Exception(); > $field =3D $value; > } > } > > I realise now that this isn't actually how the current implementation wor= ks, but again I wanted to illustrate where I'm coming from: that backed pro= perties are just a convenience, not a different type of property with its o= wn rules. That's not really how we think about it. Our design decisions have been guided by a few factors: 1. The RFC intentionally makes plain properties and properties with hooks as fully compatible as possible. A subclass can override a plain property by adding hooks to it. Many other languages only allow doing so if the parent property already has generated accessors (`{ get; set; }`). For many of them, switching from a plain property to one with accessors is actually an ABI break. One requires generating assembly/IR instructions that access a field in some structure, the other one is a method call. This is not relevant in our case. In most languages, a consequence of `{ get; set; }` is that such properties cannot be passed by reference. This part _is_ relevant to PHP, because PHP makes heavy use of explicit by-reference passing for arrays, but not much else. However, as outlined in the RFC, arrays are not a good use-case for hooks to begin with. So instead of fragmenting the entirety of all PHP code bases into plain and `{ get; set; }` properties where it doesn't actually make a semantic difference, and then not even using them when it would matter (arrays), we have decided to avoid generated hooks altogether. The approach of making plain and hooked properties compatible also immediately means that a property can have both a "backing value" (inherited from the parent property) and hooks (from the child property). This goes against your model that backed properties are really just two properties, one for the backing value and a virtual one for the hooks. Our approach has the nice side effect of properties only containing hooks when they actually do something. We don't need to deal with optimizations like "the hook is auto-generated, revert to accessing the property directly to make it faster", or even just having the generated hook taking up unnecessary memory. You can think of our properties this way: ```php class Property { public ?Data $storage; public ?callable $getHook; public ?callable $setHook; public function get() { if ($hook =3D $this->getHook) { return $hook(); } else if ($storage) { return $storage->get(); } else { throw new Error('Property is write-only'); } } public function set($value) { if ($hook =3D $this->setHook) { $hook($value); } else if ($storage) { $storage->set($value); } else { throw new Error('Property is read-only'); } } } ``` Properties can inherit both storage and hooks from their parent. Hopefully, that helps with the mental model. Of course, in reality it is a bit more complicated due to guards and references. 2. Although you say backed properties are just syntactic, they really are not. For example, renaming a public property, making it private and replacing it with a new passthrough virtual property breaks serialization, as serialization works on the object's raw values. On the other hand, adding a hook to an existing property doesn't influence its backing value, so there is no impact on serialization. > > Being the same also makes the language more predictable, which is also = a design goal for this RFC. (Hence why "this is the same logic as methods/= __get/other very similar thing" is mentioned several times in the RFC. Con= sistency in expectations is generally a good thing.) > > I can only speak for myself, but my expectations were based on: > > a) How __get and __set are used in practice. That generally involves read= ing and writing a private property, of either the same or different name fr= om the public one; and that private property is visible everywhere equally,= no special handling based on the call stack. > > b) What happens if you accidentally cause infinite recursion in a normal = function or method, which is that the language eventually hits a stack dept= h limit and throws an error. > > So the assertion that the proposal was consistent with expectations surpr= ised me. It feels to me like something that will seem surprising to people = when they first encounter it, but useful once they understand the implicati= ons. Guards are used for dynamic property creation within `__get`/`__set`: https://3v4l.org/6u3SR#v8.3.4 When `__get` or `__set` are called, the object remembers that this property is being accessed via magic method. When you're already inside this magic method, another call will not be triggered, thus falling back to accessing the actual property of the object. In this case, this means adding a dynamic property. Dynamic properties are not particularly relevant today. The point was not to show how similar these two cases are, but to explain that there's an existing mechanism in place that works very well for hooks. We may invent some new mechanism to access the backing value, like `field =3D 'value'`, but for what reason? This would only make sense if the syntax we use is useful for something else. However, given that without guards it just leads to recursion, which I really can't see any use for, I don't see the point. Ilija