Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:120477 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 89321 invoked from network); 30 May 2023 20:45:31 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 30 May 2023 20:45:31 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 307A8180382 for ; Tue, 30 May 2023 13:45:29 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS, SPF_NONE,T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS29838 64.147.123.0/24 X-Spam-Virus: No X-Envelope-From: Received: from wout5-smtp.messagingengine.com (wout5-smtp.messagingengine.com [64.147.123.21]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 30 May 2023 13:45:28 -0700 (PDT) Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.west.internal (Postfix) with ESMTP id 53A923200906 for ; Tue, 30 May 2023 16:45:25 -0400 (EDT) Received: from imap50 ([10.202.2.100]) by compute4.internal (MEProxy); Tue, 30 May 2023 16:45:25 -0400 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:sender:subject:subject:to:to; s=fm1; t=1685479524; x= 1685565924; bh=9ytLLCo6iuR9iXhALFO6YN5mHNdH/SS8XbNFSbkl+ME=; b=n NUSXWJoyrYo6qXOFFjL7WGiQpqQOWinJhVNn/qdh9MkUaMagaDcEQ4IKiMLK8dlV 1pKdPfcPkLIuh3TQ3FDBOBxowk9sw02nvQWCvEnqtjB717879wj3GneAjeI3hQNU Q4f2f6ZxktaTJVJ1sy17shSIDNxy+Hjs7t5nZzDK6aBysedaKUGXi2AWK9GasNyb XUlOee6zxY+Km72THoy4H4u1Wp+PSsR2TO8vO5Fc93k8w812yiCisy/Rvef0Ii8F tySHm1BR0mNzp8bDpRWyoWzrXblj2mOZ3a/8koAlk34YRP/lEr4sFBb9Ohkkwfzf pHUYI0KwxotPGuyYnY+kw== 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:sender:subject :subject:to:to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender :x-sasl-enc; s=fm1; t=1685479524; x=1685565924; bh=9ytLLCo6iuR9i XhALFO6YN5mHNdH/SS8XbNFSbkl+ME=; b=OBZdbp1xmwDlj48V2H0g26qkDWkRO RLssr96KaJcqYx7chZ0vEC6kXgIyhNL2F0QuBljjYEF3b9v+aUTyiF+QV9iRK5a4 uLwnA7XM/qOo8MZAFyWqDvJLmw0f6tBYtqhWOSUvopPmQGZL7nJ7jV+f2e3+5/Ah TcdgAOyPQ4hvhU+ZGWfUDPysj8H0blWNPRcaeL5ucO0KfrmXScXAt2Vdpji9A+pF Jb1GgnQ8grcpdJpYtceQq7p3RY7eF/x5nYY7/1UnogUOibzTAbPTNCI4jjalOSqg gMVQBuZlSdbcfrYosv7aV3sx7xcrSaomwqiOGvFjaYzvdWpfRgXZcmM4g== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvhedrfeekjedgudehvdcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enucfjughrpefofgggkfgjfhffhffvufgtsehttdertderredtnecuhfhrohhmpedfnfgr rhhrhicuifgrrhhfihgvlhgufdcuoehlrghrrhihsehgrghrfhhivghlughtvggthhdrtg homheqnecuggftrfgrthhtvghrnhepkeetleehffegfedvudefueejkefhvddtueeuvdev teeiveehledugeejudfhhfetnecuffhomhgrihhnpehgihhthhhusgdrtghomhenucevlh hushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehlrghrrhihsehg rghrfhhivghlughtvggthhdrtghomh X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id A694C1700093; Tue, 30 May 2023 16:45:24 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.9.0-alpha0-447-ge2460e13b3-fm-20230525.001-ge2460e13 Mime-Version: 1.0 Message-ID: In-Reply-To: References: <64EAB991-7082-47B8-B546-73CD08243C6E@koalephant.com> <23DF3A9A-9F11-4786-A011-41AE0FA9CFA9@koalephant.com> <75a8db86-c91d-4f5e-b19e-e968926b4e8a@app.fastmail.com> Date: Tue, 30 May 2023 20:45:01 +0000 To: "php internals" Content-Type: text/plain Subject: Re: [PHP-DEV] Declaration-aware attributes From: larry@garfieldtech.com ("Larry Garfield") On Tue, May 30, 2023, at 7:34 PM, Andreas Hennings wrote: > On Tue, 30 May 2023 at 19:12, Larry Garfield wrote: >> >> I've run into this issue in my attribute library as well (https://github.com/Crell/AttributeUtils). What I do there is allow attributes to opt-in to a callback method via interface. For example: >> >> #[\Attribute] >> class AttribWithName implements FromReflectionClass >> { >> public readonly string $name; >> >> public function __construct(?string $name = null) >> { >> if ($name) { >> $this->name = $name; >> } >> } >> >> public function fromReflection(\ReflectionClass $subject): void >> { >> $this->name ??= $subject->getShortName(); >> } >> } > > So it is technically from outside it is a setter, whereas from inside > it is not really. > Technically, this means you need a version of the attribute object > that can exist without the values from the reflector. > The constructor needs to initialize some "stub" values, until the > setter is called. > Every other method also needs to support the case when the setter has > not been called yet, or possibly throw an exception. > Also, your property type has to allow null, so `?string`, not `string`. Except the property type is not null above? The argument is, but not the property. (I could use CPP here with a null value instead, if we had asymmetric visibility.) Also, if the interface is called by the reflection system itself as part of getInstance() then yes, we can guarantee that it's been run, just like we can guarantee the constructor has run. >> (Side note: This is why static analysis tools that forbid writing to readonly properties except from the constructor are wrong; it's also an example of where asymmetric visibility would be superior to readonly. But I digress.) > > Technically there is no guarantee that the setters will be called > before any other method, and only once. > If these methods can write on readonly properties, then any method can. > Unless we somehow mark these methods as special. There's nothing special about readonly properties there. An uninitialized non-readonly property is no more or less susceptible to still being uninitialized when you need it. Singling out readonly here is just dumb, and exacerbates the problems of readonly. > On the other hand, a wither method with "clone with" should be allowed > to work on readonly properties. > You could rewrite your method like this, once we have clone with: > (or use the old-school syntax but without readonly) > > public function withReflector(\ReflectionClass $subject): static > { > return ($this->name !== NULL) > ? $this > : clone $this with (name: $subject->getShortName(); > } > > Then in the discovery code: > > $attribute = $reflectionClass->getAttributes()[0]->newInstance(); > $attribute = $attribute->withReflector($reflectionClass); In that library I actually have several opt-in post-constructor setup routines. The documentation covers them all. Making them all withers would be just needlessly slow. Basically it's an example of a "mutable and then locked" object, which I emulate by virtue of only calling the setup methods from the library, and using readonly properties. >> That's why I think an opt-in interface is the way to go. It also avoids any confusion around the constructor parameters, which is, based on this thread, a confusing area. :-) > > What do you think about a placeholder syntax to avoid confusion with a > skipped parameter? > Like > > #[A('x', ?', 'y')] Oh god no. For one, function calls are already complicated enough and the next thing we should add there is some kind of partial application, which already discussed using ?. Second, that the attribute cares about what it is attached to is not something the caller should care about. If the user of the attribute needs to specify "and put the reflection object here" in the attribute usage, we've failed. I'd vote against any such proposal in any form, whatever the syntax. >> My second preference would be the ReflectionAttribute::inProgress() call in the constructor, or something like that. I like that less because it's a stateful call, but it would also reduce the issue with readonly properties (as in the example above) by making both alternatives available in the constructor, so maybe it's an acceptable tradeoff. > > I would like to avoid anything that is stateful or that leaves an > incomplete stub object. > (But I think I made this clear enough, so..) I don't think it's going to be possible to have both. We either have a static call of some kind (the inProgress() method above or similar) that only works sometimes (ie, while an attribute is being constructed), or we have multiple setup methods (which the engine can ensure are called, but technically that does mean the object has a moment where it's potentially incomplete). I doubt we can have our cake and eat it too, here. Personally I think the "thou shalt not ever have an object that isn't perfectly setup" rule is over-blown. It's a good guideline, but it has edge cases where it just simply doesn't work. This is one of them. --Larry Garfield