Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:120444 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 88301 invoked from network); 30 May 2023 01:02:48 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 30 May 2023 01:02:48 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 82CD61804D7 for ; Mon, 29 May 2023 18:02:47 -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-yw1-f170.google.com (mail-yw1-f170.google.com [209.85.128.170]) (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 ; Mon, 29 May 2023 18:02:46 -0700 (PDT) Received: by mail-yw1-f170.google.com with SMTP id 00721157ae682-565cdb77b01so34113017b3.0 for ; Mon, 29 May 2023 18:02:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dqxtech-net.20221208.gappssmtp.com; s=20221208; t=1685408566; x=1688000566; h=to:subject:message-id:date:from:in-reply-to:references:mime-version :from:to:cc:subject:date:message-id:reply-to; bh=JViQmNaGFR3a38nP9VU/JT8mhL6qOB6E2BxdMnEaH0w=; b=sg/pm31ewW5ajFS8rzeUNdeEwZPjfkfj9dW0daHZqu3v69I33s8nJbUl0p+XD0l8QQ pr7231f0p/oq5KmQkpMUx+f9M5Gq9FzOkINGu2Dk49bThwRI9taXLYv7iqbX5Asy/vMC uELmy3FDNhhASqLBQMkbTvt2w5OWCqntC3O/tm3AMsoFK5wG7tLIYZ+yoayHBlMlH7c1 yMkgYHd0K2AjAJOTmiOGDqQLBZf4CYbrZ1r7gfHfb3Cdyqz778otdcv8XmPSRS874yhf ApBTUSFw62QbOkk0cceq4xSWjhdPI1OQRmJdnuvYWl0l/AxRJiCq9uBT1M1auRdgIR3Z tuEg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685408566; x=1688000566; h=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=JViQmNaGFR3a38nP9VU/JT8mhL6qOB6E2BxdMnEaH0w=; b=YEzYSOU+Sjwbevcws9MWpyL3KFNc5v+AVXAppVOjhejaAvFWWy+/dC333NgCAcOhNi t4rp+IqmvWdhUyI7b2inwTzVWSYVMj0OgCwSV+1k5+jJzDlBKwhQd97FgEx/QJ03KQBB G8ZBC+j7PViJoUW3glL1A6NdilSoRYN3L+G6K5faBgBkECtXf/1Ued+VA10OXubqx05Z o2WEH8MybJpVltDwJilwMWozRWG9rD/I/++4Z9rkC+Vz/9R/0Yu/uWZxgbVmTm64UF1u 2ALtKfsa1a7c+sthxdQQ/Ztv6qEYUkmZTE4l9WNEZExE+5y90DQ3brE0RH2SYKIs6sf0 alQw== X-Gm-Message-State: AC+VfDxHlMTEXHvyKWljYME0cEITNMRgfwDefUKtZ2e9Z8ZEq8r9Km9b 0GQHWNQvQYT3EH8yvlJXfBZ5mOG21+SpKM1q7iyLoZVwDJpXAWLiuJY= X-Google-Smtp-Source: ACHHUZ6mU1iGjm8Hluwx0eoxQSB663zyJnqAZVFuo/1tQPM1ieaiTo41k6dsZw/zE6WsEZDfH/Dcy5UeyZagwGB4g6A= X-Received: by 2002:a81:c310:0:b0:55a:ad7d:3f38 with SMTP id r16-20020a81c310000000b0055aad7d3f38mr519598ywk.26.1685408566098; Mon, 29 May 2023 18:02:46 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: Date: Tue, 30 May 2023 03:02:35 +0200 Message-ID: To: PHP internals Content-Type: text/plain; charset="UTF-8" Subject: Re: Declaration-aware attributes From: andreas@dqxtech.net (Andreas Hennings) A big TBD would be the new attribute classes we need to mark these parameters. Perhaps we could use just one attribute class, e.g. "AttributeDeclaration", and use the parameter type to determine which part of the declaration is expected. Also, if an attribute is allowed on different symbol types, then these parameters could be made nullable, or allow multiple types. Of course some combinations would be ambiguous, e.g. `\ReflectionClass|\ReflectionMethod`. #[Attribute(Attribute::TARGET_METHOD, Attribute::TARGET_PROPERTY, Attribute::TARGET_CLASS_CONSTANT)] class A { public function __construct( public readonly string $x, #[AttributeDeclaration] public readonly \ReflectionClass $class, #[AttributeDeclaration] public readonly ?\ReflectionMethod $method, #[AttributeDeclaration] public readonly ?\ReflectionClassConstant|\ReflectionProperty $constantOrProperty, public readonly string $y, ) {} } On Tue, 30 May 2023 at 02:48, Andreas Hennings wrote: > > Hello internals, > I am picking up an idea that was mentioned by Benjamin Eberlei in the past. > 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 = (new ReflectionClass(C::class))->getAttributes()[0]->newInstance(); > assert($a instanceof A); > assert($a->x === 'x'); > assert($a->class->getName() === 'C'); > assert($a->y === '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 = (new ReflectionMethod(CC::class, 'f))->getAttributes()[0]->newInstance(); > assert($a->class->getName() === 'CC'); > assert($a->method->getName() === '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