Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:120471 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 71641 invoked from network); 30 May 2023 17:12:18 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 30 May 2023 17:12:18 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 712C81804D7 for ; Tue, 30 May 2023 10:12:17 -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 wout1-smtp.messagingengine.com (wout1-smtp.messagingengine.com [64.147.123.24]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits)) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 30 May 2023 10:12:16 -0700 (PDT) Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailout.west.internal (Postfix) with ESMTP id A398D32008FC for ; Tue, 30 May 2023 13:12:15 -0400 (EDT) Received: from imap50 ([10.202.2.100]) by compute4.internal (MEProxy); Tue, 30 May 2023 13:12:15 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= garfieldtech.com; h=cc:content-transfer-encoding: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=1685466735; x=1685553135; bh=gfJ6JoXoUG vJvL8a9T+7gX4RICB4LJOfU3WAzplJhVc=; b=SOTTWYf8oX6DqxwNUNTx0u7ZyM U1nc1MzsrTv/9F3TdZJAimeBOJsDx66nCXJ2q3icxsuQ/otrECz0FcTJRjvJV4lV qbafCFg/XwUHyCekc5iDMmxoE8Fkq9Z2LfyOvDYokLehVz1Lc1+L/cBepSL1TtRV hv+aznI7QPzgLHtrWkfPkDeh8f6Z0b4dAw54SkYoNM7OWlKxgIh8if8+Y7X4Gcyc vXbiLK2CvutG0JlJ6CrQArMv1JdPIfV46b5AzOQaw7/U44k0g0FOoDkvdW2a8OJW 8LgbO9o07foXo2wQSGwOTbZ10B+k0FAB4cNUcmMxqo6cQw7awD0qL2isbaYA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding: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=1685466735; x= 1685553135; bh=gfJ6JoXoUGvJvL8a9T+7gX4RICB4LJOfU3WAzplJhVc=; b=p 26U4664w2H2Zrdf4fpK/pCfrDDbPga5PXHv5KNU8XEKgMaf67JhOOuqIFuBH+QbJ G7GDA1GGb8CqHLsRuffq/ssimA99Ja+CafV28K9Hs1ios8FwGyAs4V4Swbm2Vy7X 4M8BnWGna3PE9obILsMjX1l5s7RlJ6Mcg03SivE2poy8XaZiOcKDrYkLJU20qJ8Y gQX8xok7Wfv8Ud8jHk344MZzhukx3d2CT9jUlH2+Oh4MxZcXE9saYWnCGUqdLKWy MUvNxzOtiM/yn9qQo+2yLp32FBQrgEMZ40Wv/60yemk1X/GlcIuRPH4NObIUS/5P z/8PzvxKqIK9AGEdBCQbw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvhedrfeekjedguddtkecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd enucfjughrpefofgggkfgjfhffhffvufgtgfesthhqredtreerjeenucfhrhhomhepfdfn rghrrhihucfirghrfhhivghlugdfuceolhgrrhhrhiesghgrrhhfihgvlhguthgvtghhrd gtohhmqeenucggtffrrghtthgvrhhnpefhkedtfffghfekieduhedvheefgfefheeugfdv leetteektefgteejieeltdelkeenucffohhmrghinhepghhithhhuhgsrdgtohhmnecuve hluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheplhgrrhhrhies ghgrrhhfihgvlhguthgvtghhrdgtohhm X-ME-Proxy: Feedback-ID: i8414410d:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id 052CE1700093; Tue, 30 May 2023 13:12:14 -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: <75a8db86-c91d-4f5e-b19e-e968926b4e8a@app.fastmail.com> In-Reply-To: References: <64EAB991-7082-47B8-B546-73CD08243C6E@koalephant.com> <23DF3A9A-9F11-4786-A011-41AE0FA9CFA9@koalephant.com> Date: Tue, 30 May 2023 17:11:54 +0000 To: "php internals" Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] Declaration-aware attributes From: larry@garfieldtech.com ("Larry Garfield") On Tue, May 30, 2023, at 2:49 PM, Andreas Hennings wrote: > On Tue, 30 May 2023 at 15:14, Stephen Reay = wrote: >> >> (Resending to the list without all the history because qmail complain= ed about message size) >> >> >> >> >> >> Hi Andreas, >> >> >> >> I too have wondered (and I think asked in room11?) about such a co= ncept. >From memory the general response was =E2=80=9Cjust do it in user= land with a wrapper=E2=80=9D so its good to see someone else is interest= ed in this being part of the language. >> >> >> >> While I agree that it=E2=80=99s most useful if the `Reflector` ins= tance is available in the constructor, I=E2=80=99m not keen on the propo= sed magic =E2=80=9Cskipping=E2=80=9D of arguments as you suggest. It see= ms way too easy to confuse someone (particularly if the attribute class = itself has reason to be instantiated directly in code) >> > >> > Good point! Almost made me change my mind completely. But I already >> > changed it back :) >> > >> > When instantiating in code, the "real" signature would have to be >> > used, and the reflector argument passed explicitly. >> >> >> That=E2=80=99s kind of my point: it=E2=80=99s not super intuitive why= (or the specifics of how) it=E2=80=99s being skipped when it=E2=80=99s = an attribute, vs when it=E2=80=99s instantiated from code. What if someo= ne specifies an argument with the same name? If they specify args withou= t names, can they just use null for that? Etc. > > I agree it could be confusing. > But for the named args, I think it is quite obvious: > > #[Attribute] > class A { > public readonly array $moreArgs; > public function __construct( > public readonly string $x, > // Reflector parameter can be last required parameter, BUT > #[AttributeDeclaration] public readonly \ReflectionClass $class, > // Optional parameters have to be after the required reflector par= ameter. > public readonly ?string $y =3D NULL, > // Variadic parameter must be last. > string ...$moreArgs, > ) { > $this->moreArgs =3D $moreArgs; > } > } > > #[A('x', 'y', 'z')] // -> new A('x', $reflector, 'y', 'z') > #[A(x: 'x', y: 'y')] // -> new A('x', $reflector, 'y') > #[A(x: 'x', class: new ReflectionClass('C'))] // -> new A('x', new > ReflectionClass('C')) > > We _could_ say that explicitly passing a value for the reflector in an > attribute declaration is forbidden. > Or we allow it, then an explicit value would simply overwrite the > implicit value. > > If we use placeholder syntax, the above examples would look like this: > > #[A('x', ?, 'y', 'z')] // -> new A('x', $reflector, 'y', 'z') > #[A(x: 'x', class: ?, y: 'y')] // -> new A('x', $reflector, 'y') > #[A(x: 'x', class: new ReflectionClass('C'))] // -> new A('x', new > ReflectionClass('C')) > > >> >> > This would be >> > useful for unit tests that want to replicate the realistic behavior. >> > Also it guarantees that the code of the attribute class can really >> > count on this value to not be null, no matter how the class is >> > instantiated. >> > >> > >> >> I would expect that whether the Reflector object is required is simpl= y a matter of whether or not the parameter is nullable. >> If it=E2=80=99s not nullable, then yes, the explicit instantiation ca= ll will need to supply it at the correct location. If it=E2=80=99s only = required when created from attribute usage, then it would accept null, a= nd the constructor would have appropriate logic to handle that. >> > > Yes. > But I would expect the common practice to be to make it required, > because then the constructor code will be simpler. > >> >> >> >> >> >> I think a better approach would be to suggest authors put the para= meter at the *end* of the parameter list, so that no =E2=80=98skipping' = is required when passing arguments without names (or put it where you li= ke if you=E2=80=99re always using named arguments) >> > >> > If I understand correctly, the proposal would technically not chang= e, >> > we just add a recommendation. >> >> Technically, yes =E2=80=9Cmy way=E2=80=9D would work fine with the pr= oposal you=E2=80=99ve suggested, if I choose to always put the parameter= marked by #[ReflectionContext] last. > > As above, the problem with this would be optional and variadic > parameters, which have to come after a required reflector parameter. > >> >> I=E2=80=99m just concerned about confusing usage if =E2=80=9Cinsert t= his parameter anywhere=E2=80=9D is the =E2=80=98recommended=E2=80=99 (i.= e. documented example) way to use this feature. >> >> Even with that concern, I still prefer this to most other solutions m= entioned so far, for the same reasons: they=E2=80=99re all some degree o= f magic. >> >> The only other solution I can think of that=E2=80=99s less =E2=80=9Cm= agic=E2=80=9D and more explicit, is (and I have no idea if this is even = feasible technically) to introduce a builtin trait for attribute classes= to use, providing a protected method or property that gives access to t= he Reflector (how the trait has access is not really important, I assume= it can be assigned to the object somehow before the constructor is call= ed). I guess this could also be an abstract class, but a trait makes it = much easier to adopt so that would be my preferred approach. >> >> So something like >> >> trait AttributeReflector { >> protected function getReflector(): \Reflector { >> // do internal stuff >> } >> } >> >> #[Attribute] >> class Foo { >> Use \AttributeReflector; >> >> public readonly string $name; >> >> function __construct(?string $name =3D null) { >> $this->name =3D $name ?? $this->getReflector()->name; >> } >> } > > I was also considering this, but there is a problem. > When you instantiate the attribute class outside an attribute > declaration, how do you tell it about a required reflector? > This would be relevant e.g. during unit tests. > > The constructor parameter provides a clean way to pass a custom reflec= tor. > But with the ->getReflector(), the reflector would already be > magically added to the object before the constructor is executed. This > would be impossible to replicate in custom code outside an attribute > declaration. 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=20 { public readonly string $name; =20 public function __construct(?string $name =3D null)=20 { if ($name) { $this->name =3D $name; } } =20 public function fromReflection(\ReflectionClass $subject): void { $this->name ??=3D $subject->getShortName(); } } (Side note: This is why static analysis tools that forbid writing to rea= donly properties except from the constructor are wrong; it's also an exa= mple of where asymmetric visibility would be superior to readonly. But = I digress.) My preference would be for something along those lines to be implemented= in core. Importantly, we *MUST NOT* design it such that the reflection object get= s assigned to a property of the attribute. Reflection objects are not s= erializable. Attributes will frequently be cached. That means it force= s the attribute author to make the property nullable AND then unset it s= ometime before the attribute gets serialized, or it will break. That's = a no-go. 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 thi= s thread, a confusing area. :-) My second preference would be the ReflectionAttribute::inProgress() call= in the constructor, or something like that. I like that less because i= t's a stateful call, but it would also reduce the issue with readonly pr= operties (as in the example above) by making both alternatives available= in the constructor, so maybe it's an acceptable tradeoff. I can see an argument either direction here. --Larry Garfield