On Sat, Jul 5, 2025, at 00:47, Andreas Hennings wrote:
> On Sat, 5 Jul 2025 at 00:11, Rob Landers <rob@bottled.codes> wrote:
> >
> >
> >
> > On Fri, Jul 4, 2025, at 17:21, Andreas Hennings wrote:
> >
> > On Fri, 4 Jul 2025 at 06:30, Stephen Reay <php-li...@koalephant.com> wrote:
> > >
> > >
> > >
> > > On 4 Jul 2025, at 00:54, Andreas Hennings <andr...@dqxtech.net> wrote:
> > >
> > > On Thu, 3 Jul 2025 at 19:17, Stephen Reay <php-li...@koalephant.com> 
> > > wrote:
> > >
> > >
> > >
> > >
> > >
> > > Sent from my iPhone
> > >
> > > On 3 Jul 2025, at 23:40, 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.
> > >
> > > 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.
> > >
> > >
> > > Hi Andreas,
> > >
> > > I guess the key difference I wanted to highlight is that the existing 
> > > discussion keeps referencing it as a "setter" - which I think from user 
> > > land at least will generally be understood to mean setting a property on 
> > > an object - which then adapted into Larry's mention of specifically 
> > > setting a property before the constructor is run.
> >
> > I guess the main reason it "keeps" doing that is that an email is not
> > a wiki page that could be updated :)
> > I am happy to use different terminology in an RFC.
> > (with the current plan, it would only be mentioned under "alternatives
> > that were considered")
> > "method injection" seems fine.
> >
> > But then how would we name such a method?
> > In Larry's library it is ->fromReflection(..), but this is something I
> > would typically use for static factories.
> > To me, ->setReflector() is still ok even if internally it is not a
> > setter. The method describes the contract, not what happens inside.
> > Another idea would be ->tellAboutReflector() or maybe 
> > ->injectTargetReflector()?
> >
> > >
> > > I think it's a bad idea to reference this concept as "setting a property" 
> > > - my understanding is that it's never a good idea to hang onto Reflector 
> > > objects longer than absolutely necessary, so I don't think this feature 
> > > should then result in people doing that due to the impression it gives 
> > > (i.e. if it was referred to as "setReflection<subtype>()" or if the 
> > > description for it is "allows an Attribute to store the Reflector target 
> > > it was declared on" etc)
> > >
> >
> >
> > --------
> >
> > Now, to resolve the controversial part of this discussion.
> >
> > We could reduce the RFC to the uncontroversial part:
> > Provide a ReflectionAttribute->getTargetReflector().
> >
> > With this, the rest of the proposal can be implemented in userland
> > using debug_backtrace().
> > https://3v4l.org/Ilrqm#vnull
> >
> > #[Attribute]
> > class MyAttribute {
> >     public function __construct() {
> >         $target_reflector =
> > debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS |
> > DEBUG_BACKTRACE_PROVIDE_OBJECT, 3)[1]['object'];
> >     }
> > }
> >
> > Yes it feels dirty, but now it is now longer something we have to
> > argue about in this list :)
> >
> >
> > Why do we have to call a constructor before we initialize a property? 
> > https://3v4l.org/srdM6
> >
> > You can do this in regular php, but it is even simpler in the engine.
> >
> > — Rob
> 
> This is interesting, but I still add some logistical clutter to the
> attribute class.
> It does bring some advantage over post-construction method injection,
> because you can do the logistics in a trait, and then in the
> constructor you have all the information available.
> But, even if that part is hidden away in the engine, now we have a
> property that we probably want to unset later.
> 
> -- Andreas
> 

I haven’t read the entire thread yet, but why would you want to unset it? In my 
experience of working with attributes, they have a very short lifetime (usually 
to the end of the loop/function, and no more) since they’re just metadata. The 
biggest boon I see here is the ability to easily encapsulate some logic in my 
attributes without relying on 3rd party behavior. If some library decides to 
keep them around, I will assume they know what they’re doing.

— Rob

Reply via email to