On May 29, 2023, at 17:48, Andreas Hennings <andr...@dqxtech.net> wrote:
> 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.

+1. This is a substantial limitation in the attribute system.

> 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.

I see a third way which introduces less "magic":

3.a. Add a method to ReflectionAttribute which retrieves the target of the 
attribute as an appropriate reflection object. (Sadly, the obvious name 
"getTarget" is already taken; I'll call it "getReflectionTarget" for now.)

3.b. Add a static method to ReflectionAttribute which, when called within an 
Attribute constructor which is being called by 
ReflectionAttribute::newInstance(), returns the ReflectionAttribute object 
which is being instantiated.

These features could be used together to set a default property on an attribute 
based on its target, e.g.

    #[Attribute(Attribute::TARGET_PROPERTY)]
    class PropertyAnnotation {
      public string $name;
  
      public function __construct(?string $name = null) {
        $this->name = $name ?? 
ReflectionAttribute::underConstruction()->getReflectionTarget()->getName();
      }
    }

Another variant that comes to mind is:

3.b. Add a new flag to attributes which causes the ReflectionAttribute object 
to be passed to the constructor as the first argument, e.g.

    #[Attribute(Attribute::TARGET_PROPERTY | Attribute::WHO_MADE_ME)]
    class PropertyAnnotation {
      public string $name;

      public function __construct(ReflectionAttribute $attr, ?string $name = 
null) {
        $this->name = $name ?? $attr->getReflectionTarget()->getName();
      }
    }

This is a little messier because it can't be used "under the covers" by an 
attribute base class, but it accomplishes the same goals.
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to