Re: [RFC idea] Target-aware attributes
| From: | Andreas Hennings | Date: | Thu, 03 Jul 2025 17:54:56 +0000 |
| Subject: | Re: [RFC idea] Target-aware attributes | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-127868@lists.php.net to get a copy of this message | ||
On Thu, 3 Jul 2025 at 19:17, Stephen Reay <php-lists@koalephant.com> wrote:
>
>
>
>
> Sent from my iPhone
> > On 3 Jul 2025, at 23:40, Larry Garfield <larry@garfieldtech.com> wrote:
> >
> > On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
> >> This topic was discussed in the past as "Declaration-aware
> >> attributes", and mentioned in the discussion to "Amendments to
> >> Attributes".
> >> I now want to propose a close-to-RFC iteration of this.
> >> (I don't have RFC Karma, my wiki account is "Andreas Hennings
> >> (donquixote)")
> >>
> >> -----
> >>
> >> Primary proposal
> >> =============
> >>
> >> I propose to introduce 3 new methods on ReflectionAttribute.
> >>
> >> static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
> >> Most of the time, this will return NULL.
> >> During the execution of ReflectionAttribute->newInstance(), it will
> >> return the reflector of the symbol on which the attribute is found.
> >> (in other words, during
> >> $reflector->getAttributes()[$i]->newInstance(), it will return
> >> $reflector.)
> >> During the execution of
> >> ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
> >> will return $target.
> >> If the call stack contains multiple calls to the above mentioned
> >> methods, only the closest/deepest one counts.
> >> (This means that php needs to maintain a stack of reflectors.)
> >
> > *snip*
> >
> >> Other alternatives
> >> ======================
> >>
> >> In older discussions, it was suggested to provide the target reflector
> >> as a special constructor parameter.
> >> This is problematic because an attribute expression #[MyAttribute('a',
> >> 'b', 'c')] expects to pass values to all the parameters.
> >>
> >> Another idea was to provide the target reflector through a kind of
> >> setter method on the attribute class.
> >> This can work, but it makes attribute classes harder to write, because
> >> the constructor does not have all the information.
> >> It may also prevent attribute classes from being stateless (depending
> >> how we define stateless).
> >>
> >>
> >> Userland implementations
> >> =========================
> >>
> >> One userland implementation that was mentioned in this list in the
> >> past is in the 'crell/attributeutils' package.
> >> This one uses a kind of setter injection for the target reflector.
> >> See
> >>
> >> https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.php
> >
> > Hey, I know that guy! :-)
> >
> >> Another userland implementation is in the
> >> 'ock/reflector-aware-attributes' package.
> >> https://github.com/ock-php/reflector-aware-attributes
> >> (I created that one)
> >> This supports both a setter method and getting the target reflector
> >> from the attribute constructor.
> >>
> >> The problem with any userland implementation is that it only works if
> >> the attribute is instantiated (or processed) using that userland
> >> library.
> >> Simply calling $reflector->getAttributes()[0]->newInstance() would
> >> either return an instance that is incomplete, or it would break, if
> >> the attribute class expects access to its target.
> >
> > I am unsurprisingly in favor of finding a solution here, as there are innumerable cases
> > where you need the reflectable that the attribute is on; the most common for me is using the
> > name/type of a property as defaults for the attribute.
> >
> > However, I am very skeptical about a stateful global value as the solution. We've
> > tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels
> > like a major step backwards, and a great place for weird bugs to hide.
> >
> > A setter method injection is what I did in AttributeUtils, because it was the only real
> > option. Alternatively, I suppose core could use property setter injection (either a magically named
> > property like $__reflector, or a property that itself has an attribute on it, etc.). That would
> > allow it to be set before the constructor is called, and with property hooks would allow processing
> > either immediately or later in the constructor. The downside here is that Attribute are, generally,
> > serializable, but a Reflection object is not. So if someone wanted a serializable attribute they
> > would have to accept the property, use it, and then remember to unset it at some point..
> > That's clumsy.
> >
> > --Larry Garfield
> >
>
> As someone that's written yet another userland "solution" for this problem, I
> have an alternative solution, based somewhat on an internalised concept of "never store
> Reflectors".
>
> Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a
> single Reflector argument.
>
> If the attribute implements the interface, the method is called immediately following
> instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.
>
> Yes this means logic dependant on the reflector has to be delayed until the method is called. I
> think this is an acceptable payoff for the solution: it only applies to attributes that explicitly
> opt in to receive the Reflector, and it helps to not encourage storing the reflector in a property.
yep, same tradeoff I mentioned in the other mail.
>
> In theory I guess it could call a static named constructor, but the other arguments would have
> to be an array, which would end up being quite clunky if the goal is to derive default argument
> values from the reflector.
>
> I'm really looking forward to this feature, thanks for introducing this discussion/RFC!
>
>
>
> Cheers
>
> Stephen