Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:120452 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 28892 invoked from network); 30 May 2023 12:06:40 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 30 May 2023 12:06:40 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 108D9180384 for ; Tue, 30 May 2023 05:06:39 -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=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE, T_SCC_BODY_TEXT_LINE autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-yb1-f176.google.com (mail-yb1-f176.google.com [209.85.219.176]) (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 05:06:38 -0700 (PDT) Received: by mail-yb1-f176.google.com with SMTP id 3f1490d57ef6-bad0c4f6f50so6238939276.1 for ; Tue, 30 May 2023 05:06:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dqxtech-net.20221208.gappssmtp.com; s=20221208; t=1685448397; x=1688040397; 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=CkjU0tFNdFzV+uNurLoxQVVlGXtikQ690HgHqj1UsMs=; b=pvIfTbBo6BRVKxMbiGyPvonzOELaxk+Zmvtllvshbw4yjBiQTLNffA/ufcXG52oy5v STlHzXarMPQ+YX6Mv/x87nKR44D/I1rXGxR7An1gmmLdu3WqvPwoSyBHIS9B+SMnmT4W MI9hOINGJUILxXH4dEv7w/1od/56MGnpEB0/Xn+5L0sQn+MX8b5S9hfuKKj3Mk509Bwj CX51FU0cGpixXtSUAnCBCUshvnaT/Uyky+iNZtcwhDYMFWsjtRav6RqglbLIIzRXi+1J FYtCgCbhSecGaUPLYapb0eCas7BMgyLDj51UQqAXFb2sPQFYZmV04EBUN5No78NwprUC eUxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685448397; x=1688040397; 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=CkjU0tFNdFzV+uNurLoxQVVlGXtikQ690HgHqj1UsMs=; b=CkVFRGqjugZGE8ypAwm8UBMqjLBw+flOxjHhi8JS0b5nUVEdR8kZbPyoCIoyyD00rD hYitEu6xYj2xMDyMj5EJJkW5aBJgCXmkeszTeIy5Z4tShgK9PfFEu/UuR6z0CkdX502o hjiWU5C/N/83rbP1Bf307VNk2F3Pwkt5MTIutUjZWw8sDfjMkDM1l1w4BKqeUlv6lJbX /4d+6dBYIpsymjYnkHqJy4DDrU7PSLCfDNlTGIdEFH0pqI5gaa20ujQGXW1wKKiWQRhX pW9/VIhD1hOKASjwE961LFoUt7jsuea0XIfuTCQQPqrpwOcwcMpLpmZOQf9ErztB0Njl dWNA== X-Gm-Message-State: AC+VfDzmXia6L0I+ODxUMtg+ij60M/ZsnQijWH9r9clgfEFgRCKPbex/ 0NRe1IUUmFc3FL2E61aJ/wV8Yw5ry1JzoIb5mg2i6g== X-Google-Smtp-Source: ACHHUZ7EXaodZco+W59r/yVlzh99SH7FLFNGPGCCPgHPf40Vvc7c49uxTJe93Z0FWTyslL9eqXDKcJSmmbfc5qvUyZ0= X-Received: by 2002:a25:4c5:0:b0:b8e:ce2a:f083 with SMTP id 188-20020a2504c5000000b00b8ece2af083mr13178069ybe.26.1685448397557; Tue, 30 May 2023 05:06:37 -0700 (PDT) MIME-Version: 1.0 References: <64EAB991-7082-47B8-B546-73CD08243C6E@koalephant.com> In-Reply-To: <64EAB991-7082-47B8-B546-73CD08243C6E@koalephant.com> Date: Tue, 30 May 2023 14:06:26 +0200 Message-ID: To: Stephen Reay Cc: PHP internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] Declaration-aware attributes From: andreas@dqxtech.net (Andreas Hennings) On Tue, 30 May 2023 at 05:22, Stephen Reay wrote= : > > > > > On 30 May 2023, at 07:48, Andreas Hennings wrote: > > > > Hello internals, > > I am picking up an idea that was mentioned by Benjamin Eberlei in the p= ast. > > https://externals.io/message/110217#110395 > > (we probably had the idea independently, but Benjamin's is the first > > post where I see it mentioned in the list) > > > > Quite often I found myself writing attribute classes that need to fill > > some default values or do some validation based on the symbol the > > attribute is attached to. > > E.g. a parameter attribute might require a specific type on that > > parameter, or it might fill a default value based on the parameter > > name. > > > > Currently I see two ways to do this: > > 1. Do the logic in the code that reads the attribute, instead of the > > attribute class. This works ok for one-off attribute classes, but it > > becomes quite unflexible with attribute interfaces, where 3rd parties > > can provide their own attribute class implementations. > > 2. Add additional methods to the attribute class that take the symbol > > reflector as a parameter, like "setReflectionMethod()", or > > "setReflectionClass()". Or the method in the attribute class that > > returns the values can have a reflector as a parameter. > > > > Both of these are somewhat limited and unpleasant. > > > > I want to propose a new way to do this. > > Get some feedback first, then maybe an RFC. > > > > The idea is to mark constructor parameters of the attribute class with > > a special parameter attribute, to receive the reflector. > > The other arguments are then shifted to skip the "special" parameter. > > > > #[Attribute] > > class A { > > public function __construct( > > public readonly string $x, > > #[AttributeContextClass] > > public readonly \ReflectionClass $class, > > public readonly string $y, > > ) {} > > } > > > > $a =3D (new ReflectionClass(C::class))->getAttributes()[0]->newInstance= (); > > assert($a instanceof A); > > assert($a->x =3D=3D=3D 'x'); > > assert($a->class->getName() =3D=3D=3D 'C'); > > assert($a->y =3D=3D=3D 'y'); > > > > Note that for methods, we typically need to know the method reflector > > _and_ the class reflector, because the method could be defined in a > > base class. > > > > #[Attribute] > > class AA { > > public function __construct( > > #[AttributeContextClass] > > public readonly \ReflectionClass $class, > > #[AttributeContextMethod] > > public readonly ReflectionMethod $method, > > ) {} > > } > > > > class B { > > #[AA] > > public function f(): void {} > > } > > > > class CC extends B {} > > > > $aa =3D (new ReflectionMethod(CC::class, 'f))->getAttributes()[0]->newI= nstance(); > > assert($a->class->getName() =3D=3D=3D 'CC'); > > assert($a->method->getName() =3D=3D=3D 'f'); > > > > --- > > > > Notice that the original proposal by Benjamin would use an interface > > and a setter method, ReflectorAwareAttribute::setReflector(). > > > > I prefer to use constructor parameters, because I generally prefer if > > a constructor creates a complete and immutable object. > > > > ---- > > > > Thoughts? > > > > -- Andreas > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > To unsubscribe, visit: https://www.php.net/unsub.php > > > > Hi Andreas, > > I too have wondered (and I think asked in room11?) about such a concept. = From memory the general response was =E2=80=9Cjust do it in userland with a= wrapper=E2=80=9D so its good to see someone else is interested in this bei= ng part of the language. > > While I agree that it=E2=80=99s most useful if the `Reflector` instance i= s available in the constructor, I=E2=80=99m not keen on the proposed magic = =E2=80=9Cskipping=E2=80=9D of arguments as you suggest. It seems way too ea= sy to confuse someone (particularly if the attribute class itself has reaso= n 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. 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 think a better approach would be to suggest authors put the parameter a= t the *end* of the parameter list, so that no =E2=80=98skipping' is require= d when passing arguments without names (or put it where you like if you=E2= =80=99re always using named arguments) If I understand correctly, the proposal would technically not change, we just add a recommendation. The only problem I see is with variadic parameters, which naturally need to be last. ---- This brings me to another idea: What if instead of the parameter attribute we use a pseudo constant default value? Or some other kind of expression that is only valid in the right context. #[Attribute] class A { function __construct( .. ReflectionClass $class =3D __REFLECTOR__, .. ) {.. The "constant" would be available only as a default value, not inside the constructor body. But I already don't like this idea, because ReflectionParameter->getDefaultValue() would have to throw an exception, or ->isOptional would have to return false. Better to have some kind of placeholder in the attribute declaration, if we dont like the skipping. #[A('x', ?, 'y')] or #[A('x', __REFLECTOR__, 'y')] The '?' would be the same as proposed for first class callable partial function application. This could be interpreted as that we create a callable with only one free parameter, which php then invokes with the reflector. But even the skipping without a placeholder can be justified, because an attribute declaration is already not regular code, e.g. we don't write "new ". > > > Cheers > > Stephen >