Am 16.05.2026, 21:20:51 schrieb Larry Garfield <[email protected]>:

> On Sat, May 16, 2026, at 8:24 AM, Tim Düsterhus wrote:
>
> Hi
>
>
> Am 2026-05-12 22:37, schrieb Benjamin Außenhofer:
>
> > I am not convinced this is needed. At every call site of
>
> > $reflector->getAttributes() you could inject the reflector back into
>
> > the
>
> > attributes.
>
>
> I agree with Benjamin here and actually would go even further: Making
>
> attribute instances aware of their target feels like a layering
>
> violation. Attributes are intended to provide metadata, not behavior.
>
> The behavior can then be added by whoever is consuming the attribute.
>
>
> The RFC itself contains one example with two possible use cases:
>
>
> - Further narrowing down TARGET_CLASS targets. For that I feel the
>
> correct solution would be further splitting the target constants into
>
> TARGET_CLASS_ONLY, TARGET_INTERFACE, TARGET_TRAIT, etc.
>
>
> - Adding side-effects to a constructor, specifically side-effects that
>
> need to rely on global state. This is the layering violation I mentioned
>
> above: This kind of logic should be performed by the service that is
>
> reading out and constructing the attribute - something that necessarily
>
> exists -, not by the attribute itself.
>
>
> Best regards
>
> Tim Düsterhus
>
>
> Here's another real-world example that I use i Serde, via AttributeUtils
> (slightly modified and simplified to make it clearer):
>
> #[Attribute(Attribute::TARGET_PROPERTY)]
> class Field implements FromReflectionParameter {
>
>    public function __construct(
>        public private(set) ?string $name = null,
>        public private(set) ?string $type = null,
>    ) {}
>
>    public function fromReflectionParameter(ReflectionProperty $rProp):
> void {
>        $this->name ??= $rProp->getName();
>        $this->type ??= $rProp->getType()->getName();
>    }
> }
>
> class Example {
>    #[Field]
>    public string $a;
>
>    #[Field(name: 'second');
>    public string $b;
> }
>
> 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.


> 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();
}


> Now the same functionality is available natively without going through
> AttributeUtils.  In fact, in concept most of AttributeUtils could get
> rewritten so that instead of a bunch of triggering interfaces with multiple
> rather boilerplate methods, you could do something like this:
>
> #[Attribute(Attribute::TARGET_CLASS)]
> class SomeClass {
>
>    public readonly array $props;
>    public readonly array $consts;
>
>    public function __construct(
>        public private(set) ?string $name = null,
>    ) {
>        if ($rClass =
> ReflectionAttribute::getCurrent()->getReflectionTarget()) {
>            $this->name ??= $rClass->getName();
>
>            new AttributeUtils\GetProperties($this, $rClass, Field::class,
> fn(array $ps) => $this->props = $ps)->load();
>            new AttributeUtils\GetConstants($this, $rClass,
> ConstAttribute::class, fn(array $cs) => $this->consts = $cs)->load();
>            // ...
>        }
>    }
> }
>
> I've been toying with a new API that looks more like that, but in a
> separate method.  This would move that logic fully inside the constructor,
> and eliminate a whole bunch of noisy methods and interfaces.
>
> It's not perfect, certainly.  Constructing the attribute manually for
> testing purposes would still pose a risk of incomplete data, unless you
> account for that in the constructor.
>
> That is a very valid, relevant, and common use case, which this RFC would
> simplify.  I don't like the modal nature of it either, but so far no one
> has suggested a better alternative.  (And no, "just do it all externally
> and transfer it to some other non-attribute object" is not a better
> alternative.  It's a crapton more pointless work for no benefit that makes
> the code harder to follow.)
>
> --Larry Garfield
>

Reply via email to