Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:122532 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 00ED21AD8F6 for ; Wed, 28 Feb 2024 14:25:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1709130357; bh=lI5b6dX/Je+wlAKqyPbWJ3YO9mZtKyKLv1GVf9seyiA=; h=In-Reply-To:References:Date:From:To:Subject:From; b=LyNG9fjAmuxx8Knc6Vxyt0H+dCfuxMQAr9+oDjGSR0MfMnXjWq+A1Abcv2UP5Bx/3 v0JZX4XzBt1GqSjHyalOz+onQqsXc0xH7RcVxFi1kuC1/7ohdUtxlsP+P76/+TABZu +rhU+rFzU5p8NjEyTQfjjBXSfCl9QE7FZ03H0nGoARZLTWk7GQiT8ObOfsAa+X+s0E fYeZYbKzz4vcOkyecHStGxuTq5HvPPjsX8lToKafW01FVBYhL0YfyzqWyVh6h9VyPV QeOudKrL8riuGBXw6ra8jvXUrn+3MRCIekQG/dmVk1XMMK7DQnF8xEZ1B1HTm1Txfv aeNNtxHwDRz1w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 5B5371843BF for ; Wed, 28 Feb 2024 14:25:55 +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.9 required=5.0 tests=BAYES_20,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_PASS,SPF_NONE, T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from out4-smtp.messagingengine.com (out4-smtp.messagingengine.com [66.111.4.28]) (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 ; Wed, 28 Feb 2024 14:25:54 +0000 (UTC) Received: from compute1.internal (compute1.nyi.internal [10.202.2.41]) by mailout.nyi.internal (Postfix) with ESMTP id BE7B65C0704 for ; Wed, 28 Feb 2024 09:25:44 -0500 (EST) Received: from imap50 ([10.202.2.100]) by compute1.internal (MEProxy); Wed, 28 Feb 2024 09:25:44 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-type:content-type:date:date:from :from:in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1709130344; x= 1709216744; bh=MxqA3zwoGrocQwCxV5i5xz68c3CbK9al75k9gq4YLGM=; b=b rKcgcgxcKwoEPkjqW978nYkX+r0PxA4Zp/jWz19fiyj0PtE8xIgJH5PrXISg6Ibz I57RBsk5Ik35t3dskiHd5OEyMCsVJpidnbxFK3MPusOjUTYld3PEmWneTi4+9+1O AbRFPpT9uuTtUnjCWCqvjirUQIHMb0b9XWGIH7e9mMwmHEZ2mZmU6jyst75nQPJL zM36AhqQssoccMrKbKmSJwj9dSXsB1K3ppgV47JcxEMiIch4pIYREn67Lyb9lN0q yXYZPWI1ZdM6tQIlhoY89/6YPHrF4k2kigK8QhbLwPIQKxLIxt0vWyGlKU5IepgD DjVgFke/qj4ggGGYHWWwA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm1; t=1709130344; x=1709216744; bh=MxqA3zwoGrocQwCxV5i5xz68c3Cb K9al75k9gq4YLGM=; b=O6v8ngFZL+noGchvPWG2WWJ5adYEkHzbDN+zmkP/pgLN 3zJAV50mPMh3NRSU1j/qsyPGGqR/ofLgk17PPmlXSHNgf5X50LsDKzFRrV53ch8e iipiWP2HbqHESXZ5LcXl1hpTa/wBtC7ExUCishTeh8lBeoCubYGNWRLYXNWOHkcV F5L3A3oAigjP5pPfaV16sMLwgTms+yXuD7PI5sDLJciKspK/CfDtv76TTi1CyX61 t7M6bMyTR1/MlLr5TN6uoVUlQkY2SzUtAp1igJs/zL1OdJ1I3nHVg8S5KjOftmig 2V/C5OGFv0CCLFVQ9jgwxMk85q15YlwfqgC3UCv7EQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrgeejgdeitdcutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpefofgggkfgjfhffhffvufgtsehttdertderredtnecuhfhrohhmpedfnfgrrhhr hicuifgrrhhfihgvlhgufdcuoehlrghrrhihsehgrghrfhhivghlughtvggthhdrtghomh eqnecuggftrfgrthhtvghrnhepgeelgfekudeivddvteffueejffdthfejieevhefgffek udevkedtvdelvddvffefnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrg hilhhfrhhomheplhgrrhhrhiesghgrrhhfihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 6202C1700093; Wed, 28 Feb 2024 09:25:44 -0500 (EST) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-182-gaab6630818-fm-20240222.002-gaab66308 Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net MIME-Version: 1.0 Message-ID: In-Reply-To: <1204BFC3-B976-4FEE-BE01-E668699C84E2@koalephant.com> References: <59619244-917d-4936-8f21-2854840a9bf8@rwec.co.uk> <2299271f-50ea-48c1-81fb-b64fa10c9bbb@app.fastmail.com> <1204BFC3-B976-4FEE-BE01-E668699C84E2@koalephant.com> Date: Wed, 28 Feb 2024 08:25:24 -0600 To: "php internals" Subject: Re: [PHP-DEV] [RFC[ Property accessor hooks, take 2 Content-Type: text/plain From: larry@garfieldtech.com ("Larry Garfield") On Wed, Feb 28, 2024, at 7:49 AM, Stephen Reay wrote: *snip* >> == Re virtual properties: >> >> Ilija and I talked this through, and there's pros and cons to a `virtual` keyword. Ilija also suggested a `backed` keyword, which forces a backed property to exist even if it's not used in the hook itself. >> >> * Adding `virtual` adds more work for the developer, but more clarity. It would also mean $this->$propName or $this->{__PROPERTY__} would work "as expected", since there's no auto-detection for virtual-ness. On the downside, if you have a could-be-virtual property but never actually use the backing value, you have an extra backing value hanging around in memory that is inaccessible normally, but will still show up in some serialization formats, which could be unexpected. If you omit one of the hooks and forget to mark it virtual, you'll still get the default of the other operation, which could be unexpected. (Mostly this would be for a virtual-get that accidentally has a default setter because you forgot to mark it `virtual`.) >> * Doing autodetection as now, but with an added "make a backing value anyway" flag would resolve the use case of "My set hook just calls a method, and that method sets the property, but since the hook doesn't mention the property it doesn't get created" problem. It would also allow for $this->$propName to work if a property is explicitly backed. On the flipside, it's one more thing to think about, and the above example it solves would be trivially solved by having the method just return the value to set and letting the set hook do the actual write, which is arguably better and more reliable code anyway. >> * The status quo (auto-detection based on the presence of $this->propName). This has the advantage it "just works" in the 95% case, without having to think about anything extra. The downside is it does have some odd edge cases, like needing $this->propName to be explicitly used. >> >> I don't think any is an obvious winner. My personal preference would be for status quo (auto-detect) or explicit-virtual always. I could probably live with either, though I think I'd personally favor status quo. Thoughts from others? >> > > I agree that a flag to make the field *virtual* (thus disabling the > backing store) makes more sense than a flag to make it backed; It's > also easier to understand when comparing hooked properties with regular > properties (essentially, backed is the default, you have to opt-in to > it being virtual). I don't think the edge cases of "auto" make it > worthwhile just to not need "virtual". Uh, I can't tell if you're saying "use status quo" or "use the virtual keyword." Please clarify. :-) >> == Re reference-get >> >> Allowing backed properties to have a reference return creates a situation where any writes would then bypass the set hook, and thus any validation implemented there. That is, it makes the validation unreliable. A major footgun. The question is, do we favor caveat-emptor flexibility or correct-by-construction safety? Personally I always lead toward the latter, though PHP in general is... schizophrenic about it, I'd say. :-) >> >> At this point, we'd much rather leave it blocked to avoid the issue; it's easier to enable that loophole in the future if we really want it than to get rid of it if it turns out to have been a bad idea. >> >> There is one edge case that *might* make sense: If there is no set hook defined, then there's no set hook to worry about bypassing. So it may be safe to allow &get on backed properties IFF there is no set hook. I worry that is "one more quirky edge case", though, so as above it may be better to skip for now as it's easier to add later than remove. But if the consensus is to do that, we're open to it. (Question for everyone.) >> > > I don't have strong feeling about this, but in general I usually tend > to prefer options that are consistent, and give power/options to the > developer. If references are opt-in anyway, I see that as accepting the > trade-offs. If a developer doesn't want to allow by-ref modifications > of the property, why would they make it referenceable in the first > place? This sounds a bit like disallowing regular public properties > because they might be modified outside the class - that's kind of the > point, surely. > >> == Re >> >> == Re arrays >> >>> Regarding arrays, have you considered allowing array-index writes if >> an &get hook is defined? i.e. "$x->foo['bar'] = 42;" could be treated >> as semantically equivalent to "$_temp =& $x->foo; $_temp['bar'] = 42; >> unset($_temp);" >> >> That's already discussed in the RFC: >> >>> The simplest approach would be to copy the array, modify it accordingly, and pass it to set hook. This would have a large and obscure performance penalty, and a set implementation would have no way of knowing which values had changed, leading to, for instance, code needing to revalidate all elements even if only one has changed. >> >> Unless we were OK with that bypassing the set hook entirely if defined, which, as noted above, means any safety guarantees provided by a set hook are bypassed, leading to untrustworthy code. >> >> == Re hook shorthands and return values >> >> Ilija and I have been discussing this for a bit, and we've both budged a little. :-) Here's our counter-proposal: *snip* > I think the examples given are clear, and the lack of the top-level > short closure-esque version makes it more obvious. Forgive me, I must > have missed some of the previous comments - is there a reason the > 'full' setter can't return a value, for the sake of consistency? I > understand that you don't want "return to set" to be the *only* option, > for the sake of e.g. change/audit logging type functionality (i.e. set > and then some action to record that the change was made), but it seems > a little odd and inconsistent to me that the return value of a short > closure would be used when the return value of the long version isn't. > This isn't really a major issue, I'm just curious if there was some > explanation about it? Mainly because it introduces a lot more complexity. As far as I'm aware, determining at runtime whether or not to make use of the return value is impossible. (Ie, we cannot differentiate between "return", "no return statement", and "return null".) So it would require compile time branching to generate two different pathways after lexically detecting if there is a "return" token present somewhere in the hook body. That is probably doable, technically, but introduces more complexity to an already necessarily-large RFC. It's also something that is simple enough to add later as an option without any BC breaks or implications for other parts of the design, so it's safe to punt. (Some things are harder to punt on than others, as noted, but this one is easy/safe to skip for now.) >> == Re the $value variable in set *snip* >> So what makes the most sense to me is to keep $value optional, but IF you specify an alternate name, you must also specify a type (which may be wider). So these are equivalent: >> >> public int $foo { set (int $value) => $value + 1 } >> public int $foo { set => $value + 1 } >> >> And only those forms are legal. But you could also do this, if the situation called for it: >> >> public int $foo { set(int|float $num) => floor($num) + 1; } >> >> This "all or nothing" approach seems like it strikes the best balance, gives the most flexibility where needed while still having the least redundancy when not needed, and when a name/type is provided, its behavior is the same as for a method being inherited. >> >> Does that sound acceptable? (Again, question for everyone.) >> > > My only question with this is the same as I had in an earlier reply > (and I'm not sure it was ever answered directly?), and you allude to > this yourself: everywhere *else*, `($var)` means a parameter with type > `mixed`. Why is the type *required* here, when you've specifically said > you want to avoid boilerplate? If we're going to assume people can > understand that `(implicit property-type $value) is implicit, surely we > can also assume that they will understand "specifying a parameter > without a type" means the parameter has no type (i.e. is `mixed`). > > Again, for myself I'd be likely to type it (or regular parameters, > properties, etc) as `mixed` if that's what I want *anyway*, but the > inconsistency here seems odd, unless there's some until-now unknown > drive to deprecate type-less parameters/properties/etc. If we went this route, then an untyped set param would likely imply "mixed", just like on methods. Which, since mixed is the super type of everything, would still technically work, but would weaken the type enforcement and thus static analysis potential. (Just as a method param can be widened in a child class all the way to mixed/omitted, and it would be unwise for all the same reasons.) In the RFC as currently written, omitted means "derive from the property," which is a concept that doesn't exist in methods; the closest equivalent would be if omitting a type in a child method parameter meant "use the parent's type implicitly," which is not how that works right now. --Larry Garfield