Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127872 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 8DC481A00BC for ; Fri, 4 Jul 2025 04:30:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1751603336; bh=a2siZvMrTrRwQeNEjGOcKM9PZXzpmN04g2FCSBq8orM=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=XGCfjb3QBbvm+kPbNRLEoo8tg+sjuH6Fg62JyX/npHyvRrvp15m5R9Yc7KiqnWQUO kcL7ZWa65PhfrvlvShBGDV8Ix6uTZ0E1CwbcHglmG8nYq9/r60PHaIUxMfegvakP44 MRL8yXfmsHTAbEPG9DXKnwpJj7IkHaGDv743bGLy8VS4FQOljcnAR+taER3AbE0/s+ q4KsDxrZHiLFfIsfgVcaomq19GrTTaml3P5hIFsCLpK8b733Mn1XMUVXE8RuhRFZIo tw6JvMtx4rTbTO4jV9u68EWZm02nwxd29oHaWZSGpKmEK+MLi7D9Euii8KU7ypZVks s3Opo5abhZzlQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id C126218004E for ; Fri, 4 Jul 2025 04:28:54 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,HTML_MESSAGE, SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail1.25mail.st (mail1.25mail.st [206.123.115.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Fri, 4 Jul 2025 04:28:54 +0000 (UTC) Received: from smtpclient.apple (unknown [49.48.241.21]) by mail1.25mail.st (Postfix) with ESMTPSA id 7A5766033A; Fri, 4 Jul 2025 04:30:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=koalephant.com; s=25mailst; t=1751603444; bh=a2siZvMrTrRwQeNEjGOcKM9PZXzpmN04g2FCSBq8orM=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=FekC0CViqvlzWZt/NDU7CfiubprCT64kXm27R/uQWPf+HVI2OJrsZeslhHXH+6X1B iTpE1bhjY6VNTK+GPwPHk07ZSAF9MxvBCPT/AUSlgllvpeCi2yiUIHLCQ3Me0Nc9gH KbfrWhf7PcxLFeM7HtP0fJmYR0DoxhRrYNvRXrKUcnyzRvS/pnRMqSNNumEqEBAVkT gaTcrzbh6aJN4NBZyHuLw8DbJ3QaahzAN8UZr2Mhepdr8OO6WMSHVbtz5huMUCG4eu 8k8oDDZGiXolzcUskAbLctooFSuJ2dpEoPH4M6eHReJqdPSPpnH6gvHSi+hCwJ7o3G Mv5GH8OVmrV8A== Message-ID: <3C4ED1AE-3E25-461D-8C3C-AF954499F905@koalephant.com> Content-Type: multipart/alternative; boundary="Apple-Mail=_6C7CBCBA-1AE5-4EAB-9A79-828953729302" Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.600.51.1.1\)) Subject: Re: [PHP-DEV] [RFC idea] Target-aware attributes Date: Fri, 4 Jul 2025 11:30:24 +0700 In-Reply-To: Cc: Larry Garfield , php internals To: Andreas Hennings References: <1cfc477a-781f-40e9-9e37-dd748ef261be@app.fastmail.com> <20DACB49-3C84-4B23-98E2-35050D9EDAC9@koalephant.com> X-Mailer: Apple Mail (2.3826.600.51.1.1) From: php-lists@koalephant.com (Stephen Reay) --Apple-Mail=_6C7CBCBA-1AE5-4EAB-9A79-828953729302 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On 4 Jul 2025, at 00:54, Andreas Hennings wrote: >=20 > On Thu, 3 Jul 2025 at 19:17, Stephen Reay > wrote: >>=20 >>=20 >>=20 >>=20 >> Sent from my iPhone >>> On 3 Jul 2025, at 23:40, Larry Garfield = wrote: >>>=20 >>> =EF=BB=BFOn 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)") >>>>=20 >>>> ----- >>>>=20 >>>> Primary proposal >>>> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >>>>=20 >>>> I propose to introduce 3 new methods on ReflectionAttribute. >>>>=20 >>>> 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.) >>>=20 >>> *snip* >>>=20 >>>> Other alternatives >>>> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >>>>=20 >>>> 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. >>>>=20 >>>> 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). >>>>=20 >>>>=20 >>>> Userland implementations >>>> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D >>>>=20 >>>> 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/FromReflectionClas= s.php >>>=20 >>> Hey, I know that guy! :-) >>>=20 >>>> 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. >>>>=20 >>>> 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. >>>=20 >>> 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. >>>=20 >>> 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. >>>=20 >>> 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. >>>=20 >>> --Larry Garfield >>>=20 >>=20 >> 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". >>=20 >> Introduce an interface "ReflectorAttribute" (bike shedding to come); = which accepts a single Reflector argument. >>=20 >> If the attribute implements the interface, the method is called = immediately following instantiation. >=20 > 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. >=20 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 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()" or if the description for it is "allows an = Attribute to store the Reflector target it was declared on" etc) Cheers Stephen=20 >>=20 >> 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. >=20 > yep, same tradeoff I mentioned in the other mail. >=20 >>=20 >> 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. >>=20 >> I'm really looking forward to this feature, thanks for introducing = this discussion/RFC! >>=20 >>=20 >>=20 >> Cheers >>=20 >> Stephen --Apple-Mail=_6C7CBCBA-1AE5-4EAB-9A79-828953729302 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

On 4 Jul 2025, at 00:54, Andreas Hennings = <andreas@dqxtech.net> wrote:

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:

=EF=BB=BFOn 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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D

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
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D

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/s= rc/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 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)


Cheers

Stephen 



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

= --Apple-Mail=_6C7CBCBA-1AE5-4EAB-9A79-828953729302--