Hi
Am 2026-06-29 22:22, schrieb Rob Landers:
This was an oversight. I'll add the logic there. This is how they work:
1. Attributes on promoted primary-constructor params are reflected on
both (just like promoted properties):
1. ReflectionParameter
2. ReflectionProperty
2. Attributes on bare params remain parameter-only.
3. Target validation is context-sensitive, matching existing
constructor promotion behavior:
1. `#[ParamAttr] public int $x` appears on the property too, but
newInstance() from ReflectionProperty errors.
2. `#[PropAttr] public int $x` appears on the parameter too, but
newInstance() from ReflectionParameter errors.
4. Attributes before a class remain class attributes and are not
copied to the synthesized constructor.
The last one is debatable, but from what I can tell from grepping,
putting attributes on constructors is a very rare thing (39 out of
56,546 constructors). However, class-level attributes are a more common
thing. That being said, I'd be open to changing (4) to behave like
properties/parameters where it gets attached to the class and the
constructor.
Thank you for confirming my assumption that expectations likely differ
(https://news-web.php.net/php.internals/131610).
I would expect #[Attr] to be applied to both `Foo` and
`Foo::__construct()` for consistency with promoted properties, since
“Primary constructors” are effectively “Promoted Constructors”:
#[Attr]
class Foo(public int $x) { }
The error on the `newInstance()` will only be an issue for code that
tries to unconditionally instantiate unknown attributes, which is bound
to fail already since attributes do not need to be backed by a class -
and arguably doing so is not super useful, since you don't know the
semantics of unknown attributes. Code that knows what a given attribute
means will not try to find it on mismatching targets and thus will not
try to instantiate them either.
Best regards
Tim Düsterhus