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

Reply via email to