Re: [RFC] [Discussion] Add ReflectionAttribute::getCurrent()
| From: | Larry Garfield | Date: | Sun, 17 May 2026 03:35:37 +0000 |
| Subject: | Re: [RFC] [Discussion] Add ReflectionAttribute::getCurrent() | ||
| References: | 1 2 3 4 5 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-130919@lists.php.net to get a copy of this message | ||
On Sat, May 16, 2026, at 8:30 PM, Benjamin Außenhofer wrote:
>> In this case, the attribute needs, by definition, to know the name and type of the property
>> it's on, but that can be overridden. Any serializer or ORM is going to need to address this
>> use case in some form or another; I don't know off hand how Symfony Serializer or Doctrine
>> handle it, but in Serde I took the "setter injection" approach, triggered by the presence
>> of an interface.
>
> But you have Serde or AttributeUtils making the instances of Field or
> not? I mean you already have the code in userland already that makes
> this working, why does it need to be in core with this specific API
> that is not obvious to readers and not idiomatic PHP.
>>
>> This does work, and is in production now. But as Daniel and others have noted, it means
>> there's a gap period where the object could be in an invalid state, because construction is
>> split across multiple startup methods. (The real code has a whole lot more than just one additional
>> setter callbacks.) It also means that trying to construct the attribute object with reflection
>> yourself, rather than going through AttributeUtils' API, would lead to a broken object since
>> the secondary pseudo-constructors don't get called.
>
> I don’t think this is a powerful enough argument, there are many cases
> where there are gaps where objects are not in a valid state yet.
I have made that same point myself in the past many times; yet all the SA tools on the market right
now still yell at me for writing to a readonly property from anywhere other than the constructor.
>> What this RFC would allow is rewriting the above as:
>>
>> #[Attribute(Attribute::TARGET_PROPERTY)]
>> class Field {
>>
>> public function __construct(
>> public private(set) ?string $name = null,
>> public private(set) ?string $type = null,
>> ) {
>> if ($rProp = ReflectionAttribute::getCurrent()->getReflectionTarget()) {
>> $this->name ??= $rProp->getName();
>> $this->type ??= $rProp->getType()->getName();
>> }
>> }
>> }
>
> The problem with this code is that it needs explicit if and support to
> make it testable at all by still allowing to pass name and type in the
> constructor. Which proves Tim’s argument that this is too tightly
> coupled.
>
> With the way ReflectionAttribute defers the constuction of the
> attribute, you can close the gap yourself.
>
> if (is_a($reflectionAttribute->getName(), FromReflectionParameter::class) {
> $className = $reflectionAttribute->getName();
> $attribute = $className::fromReflectionParameter($reflector);
> } else {
> $attribute = $reflectionAttribute->newInstance();
> }
I don't see what purpose that serves...
What AttributeUtils does now is approximately this (again, simplified for clarity):
public function analyze(string $class, string $attribute) {
$rClass = new ReflectionClass($class);
$classDef = $rClass->getAttributes($attribute, ReflectionAttribute::IS_INSTANCEOF)[0] ?? new
$attribute();
if ($classDef instanceof FromReflectionClass) {
$classDef->fromReflection($rClass);
}
if ($classDef instanceof ParseProperties) {
// Use reflection to get attributes off the properties of the class.
$classDef->setProperties($properties);
}
// And similar code for other components, hard coded.
}
Again, this does work, but it means you *must* go through AU's analyze() method or nothing
works.
I fully agree with the concerns that this is too modal/global. But so far, this seems the least bad
way to address the issue of attributes being fundamentally dumb about their context. If someone has
a better approach for handling that, please please do share it.
I will also note the bonus feature in this RFC, the ReflectionAttributeTarget interface. I frankly
want that even more than the reflection target. :-) Even if this RFC fails, that should be brought
back in its own little RFC and needs to pass.
--Larry Garfield