Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128025 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 EBAA91A00BC for ; Sun, 13 Jul 2025 23:28:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752449212; bh=T9D1sB7RbCgkixBDgjb5ICEjn6TT/uscwcRk7q90XRo=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=U82uWkgAlpqLmHxcYGIJAlXTEczZGycntgJzWD/t1c59Wv59ghKC7Y0iAS9hcQOga rjs+h12TYLbghH9sGQyzdLd7cIyRJ9ukBPEWFRiGgUh7wESVJyteoSq5IcrtStmDLg uslpSj906YRzLMyvIyY4KLHthbY5/m+P2wQ9mrgaIeXrV82Iw5znCNy3aWUT7Yg1nC jvmsffWVfgLXuuFpAz5T3Mk6iaJqfOyulLRMGSODcalv52c3XiTcNSPPHiHrEOnXOd dpdwo5mUc+WIrWVdnsJOfWLvjQww93RhL4AbHIUUaoxQPbsQNJaDAkoWiXXuVB9dgZ Uvqzuwnlddz7Q== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 7135E18005D for ; Sun, 13 Jul 2025 23:26:51 +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=-3.1 required=5.0 tests=BAYES_00,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 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-qv1-f43.google.com (mail-qv1-f43.google.com [209.85.219.43]) (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 ; Sun, 13 Jul 2025 23:26:51 +0000 (UTC) Received: by mail-qv1-f43.google.com with SMTP id 6a1803df08f44-6fac7147cb8so57691246d6.1 for ; Sun, 13 Jul 2025 16:28:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752449319; x=1753054119; darn=lists.php.net; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=R8hVXjt5nKMHKXLZhNNLLyyA4GXpesdNDeFzXm4Gwfo=; b=SazI8AZiBsTPeVpNf/1z4qVw1TAZGzdZ4+LHj5wf0EsHi4wc1Joey0E+oDVT3R/17D WZ65EEuPbp0sSFcTWfzMs7ktmsDWvsa5xPKCABMI+9nKLvvKfUfCqZxL8V/vW3elO3nA +d6AuFhPjKO5JDDyBj0WgUtBe8qGqxwI69svzxGbHxOI0y0BKpUUoFui9MG4OcrqxWdz hlX1iuxrcpyAtheElhh7sfdD1/tQTBuEiWghdXagZ7Knre5uUJmXwcN7FDh1AZeoPHJi eWP0Vf58GuRqQt8iM+a0lbZ3B5SKRT2QavKZcy+H3q7kv4STY3GcY0aesoaGLLniTUP5 2q5w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752449319; x=1753054119; h=content-transfer-encoding:cc: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=R8hVXjt5nKMHKXLZhNNLLyyA4GXpesdNDeFzXm4Gwfo=; b=jNFOsvCy7N8e69j1fOYqNinVW+A2zUfsNDb6uMHHgic7nBhnpXDB67wlD8zfU4eBlU 8rsRhV4OSWHy3am87c466YMd25wUVgEppqyPo01O06cA+HxBAkibuH+buUguAsGOqCcg Id8/fEg/7q57JMWxxZF1MjO//uaZ4MZNBNn0G20vlY1O3Sd/qk3tmKgKQria/ixS1j9l QHEGJx6nZ1R1HLZ1TH6YfWqJUJkOek4EdUmUmKvcVKDXlPuY/f1Sz8JqcCRTxWF5C+Of 4UGXMdIaN3tfTXcQwWt7xE6HRvQpzJ5/TvDMde4mzyaC/prRYNyRw7wAyliv8D8QhJos C6RA== X-Gm-Message-State: AOJu0YySIA73UCnTe4h0Vv4hX7jYypzCvBTz1fVmcbW12AMg03CPOp3N jkM4wtem26WdWDxAViH7Oe9xHwXRIY/NwzN+qFp58JrcR3SNx63sQc3dasRYAcf3LadEXDjH+TU 7xJPYofvYd+IHDkHmPi7DVCJASf/NacKpWepMdcggcw== X-Gm-Gg: ASbGncsY4fsVyW+GtaJpKeFvvULZt5FqgbqjH0qgZy5Mc0VgEOg2oiP8TuS0QVkcu5+ iTSpWQno74gMtKfv00uCVnLAZNUvyVCIszDcfz1wW6jrCQAQCtYvT/iVGHb1s+Bi5ikTfGO5jmu hnlue3XZADHWXIeXLUr40t467fnLbV0yiWuwY6aCboVMpJeaUtBDpik3mYD/ykamTnCAz/OBqM8 Az/EyncL2mwWcS4Ep+zhK0tAdvpjl5gWw+62EitBA== X-Google-Smtp-Source: AGHT+IFEUWFN7XFgXdjH0cUKeiox7GJkMfEOoEkIECbbYXk/RlVOcsVlY3RsTk7On2OXXpSDoG3FtemCizNtkbQUryg= X-Received: by 2002:a05:6214:4409:b0:6fa:c2a4:f806 with SMTP id 6a1803df08f44-704a360bcd0mr154976036d6.29.1752449318653; Sun, 13 Jul 2025 16:28:38 -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> <9D5043B2-1589-4FD5-B289-6E98FB1177BE@nicksdot.dev> In-Reply-To: <9D5043B2-1589-4FD5-B289-6E98FB1177BE@nicksdot.dev> Date: Mon, 14 Jul 2025 01:28:27 +0200 X-Gm-Features: Ac12FXxvNxOmzHAiCYQCFDCUkBuhYGTwnuwjxYf3LZ422F6c9GUyf689AbDPj0U Message-ID: Subject: Re: [PHP-DEV] [RFC] Readonly property hooks To: Nick Cc: php internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable From: tovilo.ilija@gmail.com (Ilija Tovilo) Hi Nick On Fri, Jul 11, 2025 at 6:31=E2=80=AFAM Nick wrote: > >> On 8. Jun 2025, at 11:16, Larry Garfield wrote: >> >> https://wiki.php.net/rfc/readonly_hooks >> >> To not get this buried in individual answers to others: > > I came up with two alternative implementations which cache the computed `= get` hook value. > One leverages separate cache properties, the other writes directly to the= backing store. > > Links to the alternative branches can be found in the description of the = original PR. > https://github.com/php/php-src/pull/18757 I am not a fan of the caching approach. The implementation draft for this approach [^1] works by storing the assigned value in the property slot, and replacing it with the value returned from get one called for the first time. One of the issues here is that the backing value is observable without calling get. For example: ``` class C { public public(set) readonly string $prop { get =3D> strtoupper($this->prop); } } $c =3D new C(); $c->prop =3D 'foo'; var_dump(((array)$c)['prop']); // foo $c->prop; var_dump(((array)$c)['prop']); // FOO ``` Here we can see that the underlying value changes, despite the readonly declaration. This is especially problematic for things like [un]serialize(), where calling serialize() before or after accessing the property will change which underlying value is serialized. Even worse, we don't actually know whether an unserialized property has already called the get hook. ``` class C { public public(set) readonly int $prop { get =3D> $this->prop + 1; } } $c =3D new C(); $c->prop =3D 1; $s1 =3D serialize($c); $c->prop; $s2 =3D serialize($c); var_dump(unserialize($s1)->prop); // int(2) var_dump(unserialize($s2)->prop); // int(3) ``` Currently, get is always called after unserialize(). There may be similar issues for __clone(). For readable and writable properties, the straight-forward solution is to move the logic to set. ``` class C { public public(set) readonly int $prop { set =3D> $value + 1; } } ``` This is slightly differently, semantically, in that it executes any potential side-effects on write rather than read, which seems reasonable. This also avoids the implicit mutation mentioned previously. At least in these cases, disallowing readonly + get seems reasonable to me. I will say that this doesn't solve all get+set cases. For example, proxies. Hopefully, lazy objects can mostly bridge this gap. Another case is lazy getters. ``` class C { public readonly int $magicNumber { get =3D> expensiveComputation(); } } ``` This does not seem to work in the current implementation: > Fatal error: Hooked virtual properties cannot be declared readonly I presume it would be possible to fix this, e.g. by using readonly as a marker to add a backing value to the property. I'm personally not too fond of making the rules on which properties are backed more complicated, as this is already a common cause for confusion. I also fundamentally don't like that readonly changes whether get is called. Currently, if hooks are present, they are called. This adds more special cases to an already complex feature. To me it seems the primary motivator for this RFC are readonly classes, i.e. to prevent the addition of hooks from breaking readonly classes. However, as lazy-getters are de-facto read-only, given they are only writable from the extremely narrow scope of the hook itself, the modifier doesn't do much. Maybe an easier solution would be to provide an opt-out of readonly. Side note: Your implementation has a bug: ``` class C { public public(set) readonly int $prop { get =3D> $this->prop + 1; } } function test($c) { var_dump($c->prop); } $c =3D new C(); $c->prop =3D 1; test($c); // int(2) test($c); // int(3) ``` You likely need to dodge the fast path in the VM by not marking the property as "SIMPLE_GET" [^2]. Sorry if this e-mail is a bit all over the place, I had trouble structuring it in a more sensible way. Ilija [^1]: https://github.com/php/php-src/compare/master...NickSdot:php-php-src:= readonly-hooks-once [^2]: https://github.com/php/php-src/blob/4d9fc506df1131c630c530a0bfa6d0338= cffa03c/Zend/zend_object_handlers.c#L864