On Thu, 3 Jul 2025 at 18:29, Larry Garfield <la...@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.
Of course you would say that :) And I might normally agree. In this case I find the tradeoff to be acceptable, when compared to the alternatives, and when considering the most common ways this would be used. Typically this would be called in a way similar to func_get_args() or to using the $this pointer. A typical global state problem is with set_error_handler() and restore_error_handler(). Here you need to work with try/finally to fully avoid side effects. In the current proposal, that try/finally is already built-in. In general, this state will be the same before and after any operation. (I have not fully thought about generators and fibers, but I don't see this becoming a problem.) One benefit of the current proposal is that it does not introduce new language features, but really quite boring static methods and properties. > > A setter method injection is what I did in AttributeUtils, because it was the > only real option. In my experience, this alternative leads to more complexity in attribute classes. The constructor needs to populate the attribute with temporary values, which are then replaced in that separate method. (I call that a "setter", but only because it behaves like one from the outside, internally we usually don't want to store the reflector). Also, we can never be fully sure whether an attribute instance is "complete" or not. If the attribute instance is coming from $reflection_attribute->newInstance(), we can be confident that the ->injectReflector() or has been called. But if the attribute was instantiated manually with "new ...", it could as well be incomplete. With ReflectionAttribute::getCurrentTargetReflector(), we can fail early in the attribute constructor, by throwing on NULL. > 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. Exactly. -- Andreas > > --Larry Garfield