Re: Declaration-aware attributes
| From: | Benjamin Außenhofer | Date: | Wed, 31 May 2023 19:27:19 +0000 |
| Subject: | Re: Declaration-aware attributes | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-120488@lists.php.net to get a copy of this message | ||
On Tue, May 30, 2023 at 2:49 AM Andreas Hennings <andreas@dqxtech.net>
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.
>
Thank you bringing this up, the more I work with attributes the more often
this comes up. I think when we designed the attributes there was just so
little concrete exprimentation that we didn't pick this up as a serious
missing gap.
As for implementation, reviewing the whole e-mail thread, i like both:
1, ReflectionAttribute::getReflectionTarget() - this we should add no
matter what and is a no brainer
2. An argument attribute that instructs newInstance() to inject the
reflector or the ReflectionAttribute, for example #[AttributeContext] or
#[AttributeTargetReflector]
>
> ----
>
> Thoughts?
>
> -- Andreas
>
> --
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: https://www.php.net/unsub.php
>
>