Re: Declaration-aware attributes
| From: | Dusk | Date: | Tue, 30 May 2023 01:43:44 +0000 |
| Subject: | Re: Declaration-aware attributes | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-120445@lists.php.net to get a copy of this message | ||
On May 29, 2023, at 17:48, Andreas Hennings <andreas@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.