[RFC] [Discussion] Add ReflectionAttribute::getCurrent()

php.internals

Daniel Scherzer

119 days ago
Hi internals, I'd like to start the discussion for a new RFC about adding a new method, ReflectionAttribute::getCurrent(), to access the current reflection target of an attribute. * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent * Implementation: https://github.com/php/php-src/pull/21440 Thanks, -Daniel

Derick Rethans

119 days ago
On 4 May 2026 21:24:39 BST, Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:
>Hi internals, > >I'd like to start the discussion for a new RFC about adding a new method, >ReflectionAttribute::getCurrent(), to access the current reflection target >of an attribute. > >* RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent
"a new static method, ReflectionAttribute::getCurrent(), that, when called from an attribute constructor, returns a reflection object corresponding to what the attribute was applied to." This sounds like an arbitrary new rule for just this functionality. I don't think we should have special rules for a single static method call. I believe it's useful to have something like this, but I'm not in favour of this approach. Would it not be possible for this to be a normal (dynamic) method on the ReflectionAttrbute object? cheers Derick

Daniel Scherzer

119 days ago
On Mon, May 4, 2026 at 2:13 PM Derick Rethans <derick@php.net> wrote:
> On 4 May 2026 21:24:39 BST, Daniel Scherzer <daniel.e.scherzer@gmail.com> > wrote: > >Hi internals, > > > >I'd like to start the discussion for a new RFC about adding a new method, > >ReflectionAttribute::getCurrent(), to access the current reflection target > >of an attribute. > > > >* RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > > > "a new static method, ReflectionAttribute::getCurrent(), that, when called > from an attribute constructor, returns a reflection object corresponding to > what the attribute was applied to." > > This sounds like an arbitrary new rule for just this functionality. I > don't think we should have special rules for a single static method call. > > I believe it's useful to have something like this, but I'm not in favour > of this approach. > > Would it not be possible for this to be a normal (dynamic) method on the > ReflectionAttrbute object? > > cheers > Derick
I agree that this is a bit odd, but the problem with just adding a dynamic method on the ReflectionAttribute object is that, in the attribute constructor, there is no access to the ReflectionAttribute instance (unless you want to extract it from the backtrace, which we shouldn't suggest). Internally, the implementation is doing that backtrace processing in a more stable way than userland probably would. We could adjust the signature, so that there is still a static method to *get* the ReflectionAttribute, but then a normal dynamic method call on that object to get the target reflection object: ``` class ReflectionAttribute { // Call from the constructor of an attribute to get the original ReflectionAttribute instance public static function getCurrent(): ReflectionAttribute {} // After using getCurrent(), use this to get the reflection target public function getReflectionTarget(): ReflectionAttributeTarget {} } ``` but either way I think we need some kind of (C-implemented) backtrace processing, and because of that it makes sense to limit this to *just* attribute constructors so that we don't need to process backtraces to an arbitrary depth. An alternative would be to provide the reflection target (or the ReflectionAttribute instance) to the attribute constructor as a parameter, but then we need some kind of way for attribute classes to signal that they want to be given that parameter, and then you get into the weeds on how to opt-in (mark the parameter with a different attribute? add an interface, even though constructors are exempt from signature checks?) that would probably make that harder to reason with for end users. -Daniel

Rob Landers

119 days ago
On Mon, May 4, 2026, at 23:22, Daniel Scherzer wrote:
> > On Mon, May 4, 2026 at 2:13 PM Derick Rethans <derick@php.net> wrote: >> On 4 May 2026 21:24:39 BST, Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote: >> >Hi internals, >> > >> >I'd like to start the discussion for a new RFC about adding a new method, >> >ReflectionAttribute::getCurrent(), to access the current reflection target >> >of an attribute. >> > >> >* RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >> >> >> "a new static method, ReflectionAttribute::getCurrent(), that, when called from an attribute constructor, returns a reflection object corresponding to what the attribute was applied to." >> >> This sounds like an arbitrary new rule for just this functionality. I don't think we should have special rules for a single static method call. >> >> I believe it's useful to have something like this, but I'm not in favour of this approach. >> >> Would it not be possible for this to be a normal (dynamic) method on the ReflectionAttrbute object? >> >> cheers >> Derick > > I agree that this is a bit odd, but the problem with just adding a dynamic method on the ReflectionAttribute object is that, in the attribute constructor, there is no access to the ReflectionAttribute instance (unless you want to extract it from the backtrace, which we shouldn't suggest). Internally, the implementation is doing that backtrace processing in a more stable way than userland probably would. > > We could adjust the signature, so that there is still a static method to *get* the ReflectionAttribute, but then a normal dynamic method call on that object to get the target reflection object: > > ``` > class ReflectionAttribute { > > // Call from the constructor of an attribute to get the original ReflectionAttribute instance > public static function getCurrent(): ReflectionAttribute {} > > // After using getCurrent(), use this to get the reflection target > public function getReflectionTarget(): ReflectionAttributeTarget {} > } > ``` > > but either way I think we need some kind of (C-implemented) backtrace processing, and because of that it makes sense to limit this to *just* attribute constructors so that we don't need to process backtraces to an arbitrary depth. > > An alternative would be to provide the reflection target (or the ReflectionAttribute instance) to the attribute constructor as a parameter, but then we need some kind of way for attribute classes to signal that they want to be given that parameter, and then you get into the weeds on how to opt-in (mark the parameter with a different attribute? add an interface, even though constructors are exempt from signature checks?) that would probably make that harder to reason with for end users. > > -Daniel
What about simply allowing an acceptance of the ReflectionAttribute in the constructor? Engine-level injection, basically. You could have it transparent from the attribute (kinda like self from python): #[Attribute] class Att { public function __construct(RelectionAttributeTarget $self, string $name) {} } function thing(#[Att("name")] string $b) {} It'd still be something "special" but attributes are already kinda special and magical. — Rob

Daniel Scherzer

119 days ago
On Mon, May 4, 2026 at 2:47 PM Rob Landers <rob@bottled.codes> wrote:
> > > On Mon, May 4, 2026, at 23:22, Daniel Scherzer wrote: > > > On Mon, May 4, 2026 at 2:13 PM Derick Rethans <derick@php.net> wrote: > > On 4 May 2026 21:24:39 BST, Daniel Scherzer <daniel.e.scherzer@gmail.com> > wrote: > >Hi internals, > > > >I'd like to start the discussion for a new RFC about adding a new method, > >ReflectionAttribute::getCurrent(), to access the current reflection target > >of an attribute. > > > >* RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > > > "a new static method, ReflectionAttribute::getCurrent(), that, when called > from an attribute constructor, returns a reflection object corresponding to > what the attribute was applied to." > > This sounds like an arbitrary new rule for just this functionality. I > don't think we should have special rules for a single static method call. > > I believe it's useful to have something like this, but I'm not in favour > of this approach. > > Would it not be possible for this to be a normal (dynamic) method on the > ReflectionAttrbute object? > > cheers > Derick > > > I agree that this is a bit odd, but the problem with just adding a dynamic > method on the ReflectionAttribute object is that, in the attribute > constructor, there is no access to the ReflectionAttribute instance (unless > you want to extract it from the backtrace, which we shouldn't suggest). > Internally, the implementation is doing that backtrace processing in a more > stable way than userland probably would. > > We could adjust the signature, so that there is still a static method to > *get* the ReflectionAttribute, but then a normal dynamic method call on > that object to get the target reflection object: > > ``` > class ReflectionAttribute { > > // Call from the constructor of an attribute to get the original > ReflectionAttribute instance > public static function getCurrent(): ReflectionAttribute {} > > // After using getCurrent(), use this to get the reflection target > public function getReflectionTarget(): ReflectionAttributeTarget {} > } > ``` > > but either way I think we need some kind of (C-implemented) backtrace > processing, and because of that it makes sense to limit this to *just* > attribute constructors so that we don't need to process backtraces to an > arbitrary depth. > > An alternative would be to provide the reflection target (or the > ReflectionAttribute instance) to the attribute constructor as a parameter, > but then we need some kind of way for attribute classes to signal that they > want to be given that parameter, and then you get into the weeds on how to > opt-in (mark the parameter with a different attribute? add an interface, > even though constructors are exempt from signature checks?) that would > probably make that harder to reason with for end users. > > -Daniel > > > What about simply allowing an acceptance of the ReflectionAttribute in the > constructor? Engine-level injection, basically. You could have it > transparent from the attribute (kinda like self from python): > > #[Attribute] > class Att { > public function __construct(RelectionAttributeTarget $self, string $name) {} > } > > > function thing(#[Att("name")] string $b) {} > > > It'd still be something "special" but attributes are already kinda special > and magical. > > — Rob >
The problem with the lack of an explicit opt-in is that it would break existing attributes E.g. on 8.5, ReflectionAttribute::newInstance() for that example `Att` would be called with a single argument (the string), and on 8.6 it would be called with 2 arguments (the ReflectionAttributeTarget and the string) without the attribute author having, *or attribute user*, having changed anything. And you might think we could check based on parameter types, but then the attribute would be dropping support for older versions of PHP, whereas with a "magic" method the call could be conditional. Plus, just because a class *can* be used as an attribute, does not mean it *must always* be used as an attribute - and I found at least one case in symfony tests where they actually manually instantiate an attribute, https://github.com/symfony/symfony/blob/8e8f87c6fa5f47a431fc2f49bdbe601f6769e19d/src/Symfony/Component/JsonPath/Tests/Attribute/AsJsonPathFunctionTest.php . Basically, if we provided the reflection information (whether it is a ReflectionAttribute or ReflectionAttributeTarget) via a parameter, we would run into one or more of the following * without any opt-in, breaking existing attributes by adding a new unexpected parameter * with an implicit opt-in of a typed parameter, breaking backwards compatibility for attributes supporting multiple versions of PHP * with an explicit opt-in of *this parameter should have the reflection information*, similar backwards compatibility breaks * confusion for library authors about how to use the new feature I agree that this is a bit weird as a calling pattern, but I've been trying to think of an alternative for months (I first started working on this at Longhorn PHP in October) and haven't been able to come up with anything cleaner. The only slight improvement I had thought of a few months ago was the addition of the instance method on ReflectionAttribute to get the reflection target, and then separately ::getCurrent() would return the ReflectionAttribute instance, but I think some "magic" method like this is still needed. -Daniel

Larry Garfield

119 days ago
On Mon, May 4, 2026, at 4:58 PM, Daniel Scherzer wrote:
> On Mon, May 4, 2026 at 2:47 PM Rob Landers <rob@bottled.codes> wrote: >> __ >> >> >> On Mon, May 4, 2026, at 23:22, Daniel Scherzer wrote: >>> >>> On Mon, May 4, 2026 at 2:13 PM Derick Rethans <derick@php.net> wrote: >>>> On 4 May 2026 21:24:39 BST, Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote: >>>> >Hi internals, >>>> > >>>> >I'd like to start the discussion for a new RFC about adding a new method, >>>> >ReflectionAttribute::getCurrent(), to access the current reflection target >>>> >of an attribute. >>>> > >>>> >* RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >>>> >>>> >>>> "a new static method, ReflectionAttribute::getCurrent(), that, when called from an attribute constructor, returns a reflection object corresponding to what the attribute was applied to." >>>> >>>> This sounds like an arbitrary new rule for just this functionality. I don't think we should have special rules for a single static method call. >>>> >>>> I believe it's useful to have something like this, but I'm not in favour of this approach. >>>> >>>> Would it not be possible for this to be a normal (dynamic) method on the ReflectionAttrbute object? >>>> >>>> cheers >>>> Derick >>> >>> I agree that this is a bit odd, but the problem with just adding a dynamic method on the ReflectionAttribute object is that, in the attribute constructor, there is no access to the ReflectionAttribute instance (unless you want to extract it from the backtrace, which we shouldn't suggest). Internally, the implementation is doing that backtrace processing in a more stable way than userland probably would. >>> >>> We could adjust the signature, so that there is still a static method to *get* the ReflectionAttribute, but then a normal dynamic method call on that object to get the target reflection object: >>> >>> ``` >>> class ReflectionAttribute { >>> >>> // Call from the constructor of an attribute to get the original ReflectionAttribute instance >>> public static function getCurrent(): ReflectionAttribute {} >>> >>> // After using getCurrent(), use this to get the reflection target >>> public function getReflectionTarget(): ReflectionAttributeTarget {} >>> } >>> ``` >>> >>> but either way I think we need some kind of (C-implemented) backtrace processing, and because of that it makes sense to limit this to *just* attribute constructors so that we don't need to process backtraces to an arbitrary depth. >>> >>> An alternative would be to provide the reflection target (or the ReflectionAttribute instance) to the attribute constructor as a parameter, but then we need some kind of way for attribute classes to signal that they want to be given that parameter, and then you get into the weeds on how to opt-in (mark the parameter with a different attribute? add an interface, even though constructors are exempt from signature checks?) that would probably make that harder to reason with for end users. >>> >>> -Daniel >> >> What about simply allowing an acceptance of the ReflectionAttribute in the constructor? Engine-level injection, basically. You could have it transparent from the attribute (kinda like self from python): >> >> #[Attribute] >> class Att { >> public function __construct(RelectionAttributeTarget $self, string $name) {} >> } >> >> function thing(#[Att("name")] string $b) {} >> >> It'd still be something "special" but attributes are already kinda special and magical. >> >> — Rob > > > The problem with the lack of an explicit opt-in is that it would break > existing attributes E.g. on 8.5, ReflectionAttribute::newInstance() for > that example `Att` would be called with a single argument (the string), > and on 8.6 it would be called with 2 arguments (the > ReflectionAttributeTarget and the string) without the attribute author > having, *or attribute user*, having changed anything. > > And you might think we could check based on parameter types, but then > the attribute would be dropping support for older versions of PHP, > whereas with a "magic" method the call could be conditional. > > Plus, just because a class *can* be used as an attribute, does not mean > it *must always* be used as an attribute - and I found at least one > case in symfony tests where they actually manually instantiate an > attribute, > https://github.com/symfony/symfony/blob/8e8f87c6fa5f47a431fc2f49bdbe601f6769e19d/src/Symfony/Component/JsonPath/Tests/Attribute/AsJsonPathFunctionTest.php. > > Basically, if we provided the reflection information (whether it is a > ReflectionAttribute or ReflectionAttributeTarget) via a parameter, we > would run into one or more of the following > > * without any opt-in, breaking existing attributes by adding a new > unexpected parameter > * with an implicit opt-in of a typed parameter, breaking backwards > compatibility for attributes supporting multiple versions of PHP > * with an explicit opt-in of *this parameter should have the reflection > information*, similar backwards compatibility breaks > * confusion for library authors about how to use the new feature > > I agree that this is a bit weird as a calling pattern, but I've been > trying to think of an alternative for months (I first started working > on this at Longhorn PHP in October) and haven't been able to come up > with anything cleaner. > > The only slight improvement I had thought of a few months ago was the > addition of the instance method on ReflectionAttribute to get the > reflection target, and then separately ::getCurrent() would return the > ReflectionAttribute instance, but I think some "magic" method like this > is still needed. > > -Daniel
For context, there was a discussion last year about this problem space: https://externals.io/message/127853 The issue is that there's really no good, clean solution to giving an attribute access to the reflection target it is on. All options have some significant drawback. (See that thread for details.) I don't like it either, but this approach is the least-bad I can think of. If someone has a better alternative with fewer trade-offs, do share. I believe this would let me greatly simplify AttributeUtils, in practice. (Disclosure: Daniel and I discussed this issue at length back at Longhorn PHP, along with a few others.) --Larry Garfield

Daniel Scherzer

117 days ago
On Mon, May 4, 2026 at 2:13 PM Derick Rethans <derick@php.net> wrote:
> On 4 May 2026 21:24:39 BST, Daniel Scherzer <daniel.e.scherzer@gmail.com> > wrote: > >Hi internals, > > > >I'd like to start the discussion for a new RFC about adding a new method, > >ReflectionAttribute::getCurrent(), to access the current reflection target > >of an attribute. > > > >* RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > > > "a new static method, ReflectionAttribute::getCurrent(), that, when called > from an attribute constructor, returns a reflection object corresponding to > what the attribute was applied to." > > This sounds like an arbitrary new rule for just this functionality. I > don't think we should have special rules for a single static method call. > > I believe it's useful to have something like this, but I'm not in favour > of this approach. > > Would it not be possible for this to be a normal (dynamic) method on the > ReflectionAttrbute object? > > cheers > Derick
In order to reduce the scope of the weird new method, I have updated the RFC to split it up: * ReflectionAttribute::getCurrent(), when called from an attribute constructor, returns a *ReflectionAttribute* matching the usage (replacing the currently-possible hacking with backtraces) * ReflectionAttribute::getReflectionTarget() is a normal (dynamic) method returning the ReflectionAttributeTarget These are expected to be used together in the constructors of attributes, e.g. `ReflectionAttribute::getCurrent()->getReflectionTarget()`, but the normal getReflectionTarget() method is also useful and usable elsewhere. -Daniel

Benjamin Eberlei

111 days ago
Am 04.05.2026, 22:24:39 schrieb Daniel Scherzer <daniel.e.scherzer@gmail.com
>:
> Hi internals, > > I'd like to start the discussion for a new RFC about adding a new method, > ReflectionAttribute::getCurrent(), to access the current reflection target > of an attribute. > > * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > * Implementation: https://github.com/php/php-src/pull/21440 > > Thanks, > -Daniel >
Hi Daniel, I am not convinced this is needed. At every call site of $reflector->getAttributes() you could inject the reflector back into the attributes. $attributes = $reflector->getAttributes(); foreach ($attributes as $attribute) { $instance = $attribute->newInstance(); $instance->setReflector($reflector); } If you have a framework, or library, handling attributes, you could introduce an interface: ReflectionAwareAttribute with setReflector() to generalize this: foreach ($attributes as $attribute) { $instance = $attribute->newInstance(); if ($instance instanceof ReflectionAwareAttribute) { $instance->setReflector($reflector); } } The „magic" of this method knowing its context reminds me of internal functions that were en voque in PHP 3/4 times, but for example get.class(null) is not allowed anymore, func_get_args() is now superseded by …$args and so on. IFF we decide this is helpful, could we think about doing this with interface injection like the code above or AttributeUtils? greetings Benjamin

Daniel Scherzer

111 days ago
On Tue, May 12, 2026 at 1:37 PM Benjamin Außenhofer <kontakt@beberlei.de> wrote:
> > > Am 04.05.2026, 22:24:39 schrieb Daniel Scherzer < > daniel.e.scherzer@gmail.com>: > >> Hi internals, >> >> I'd like to start the discussion for a new RFC about adding a new method, >> ReflectionAttribute::getCurrent(), to access the current reflection target >> of an attribute. >> >> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >> * Implementation: https://github.com/php/php-src/pull/21440 >> >> Thanks, >> -Daniel >> > > Hi Daniel, > > I am not convinced this is needed. At every call site of > $reflector->getAttributes() you could inject the reflector back into the > attributes. > > $attributes = $reflector->getAttributes(); > foreach ($attributes as $attribute) { > $instance = $attribute->newInstance(); > $instance->setReflector($reflector); > } >
This would mean that * some initialization is deferred from the attribute constructor to its `setReflector()` method * callers that fail to use `setReflector()` will be misusing the attribute, all callers need to call `setReflector()` to properly use the attribute * the internal details of the attribute (e.g. the fact that it cares about what it is applied to) get exposed by the presence of the method
> > If you have a framework, or library, handling attributes, you could > introduce an interface: ReflectionAwareAttribute with setReflector() to > generalize this: > > foreach ($attributes as $attribute) { > $instance = $attribute->newInstance(); > if ($instance instanceof ReflectionAwareAttribute) { > $instance->setReflector($reflector); > } > } > > The „magic" of this method knowing its context reminds me of internal > functions that were en voque in PHP 3/4 times, but for example > get.class(null) is not allowed anymore, func_get_args() is now superseded > by …$args and so on. > > IFF we decide this is helpful, could we think about doing this with > interface injection like the code above or AttributeUtils? > > greetings > Benjamin >
This RFC was in part motivated by simplifying AttributeUtils and allowing the language to handle some of the functionality. This kind of calling of setter methods is exactly what AttributeUtils does now. Are you suggesting that the behavior of using a setReflector() method and opting in via interfaces is what the language should do automatically when calling ReflectionAttribute::newInstance()? That would still lead to the weird partial initialization between the constructor and setReflector() calls, and while it might not be an issue for attributes created from ReflectionAttribute, it would be a footgun if attributes are manually instantiated but are expecting to have setReflector() called on them immediately. If you haven't already, I suggest checking out the thread that Larry linked to in a prior reply - https://externals.io/message/127853 has a lot of discussion of drawbacks and trade-offs. I fully agree with Larry's conclusion in https://news-web.php.net/php.internals/130773 earlier in this discussion. -Daniel

Tim Düsterhus

108 days ago
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

Daniel Scherzer

108 days ago
On Sat, May 16, 2026 at 6:24 AM Tim Düsterhus <tim@bastelstu.be> 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. >
Currently, internal validators narrow things down in the following ways beyond TARGET_CLASS: * #[\AllowDynamicProperties]: prohibits usage on traits, interfaces, readonly classes, and enums ( https://github.com/php/php-src/blob/3ed80a154d589e3ea1b02f43fa1899ea9c46d70f/Zend/zend_attributes.c#L72 ) * #[\Attribute]: prohibits usage on traits, interfaces, enums, and abstract classes ( https://github.com/php/php-src/blob/3ed80a154d589e3ea1b02f43fa1899ea9c46d70f/Zend/zend_attributes.c#L93 ) * #[\Deprecated]: prohibits usage on anything other than traits ( https://github.com/php/php-src/blob/3ed80a154d589e3ea1b02f43fa1899ea9c46d70f/Zend/zend_attributes.c#L112 ) also * #[\NoDiscard]: prohibits usage on property hooks ( https://github.com/php/php-src/blob/3ed80a154d589e3ea1b02f43fa1899ea9c46d70f/Zend/zend_attributes.c#L235 ) If we wanted to split up the TARGET_* constants to provide more granularity, we would need, at the very least * TARGET_TRAIT * TARGET_INTERFACE * TARGET_ENUM * TARGET_CLASS_ONLY but then TARGET_CLASS_ONLY would not be enough to distinguish readonly classes or abstract classes. Would we want to add dedicated constants for each of those combinations? Not to mention the fact that developers might want some other kind of validation (e.g. attribute can only be used on classes that implement some specific interface). Allowing the attribute class to do the validation itself allows much more flexibility.
> > - 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. >
Then maybe that was a bad example. Let's take a look at e.g. the symfony/console #[Option] attribute (docs: https://symfony.com/doc/current/components/console/console_arguments.html#option-attribute-constraints), which currently ( https://github.com/symfony/symfony/blob/b01d14a27dcd5ca91c5af11ca3cb41ffbe639de7/src/Symfony/Component/Console/Attribute/Option.php) uses a static constructor `tryFrom()` with a ReflectionParameter or ReflectionProperty. That static constructor does a bunch of validation, but does not rely on global state * validating that options have default values * validating the type of the property/parameter that the attribute is applied to * validating that boolean options not be nullable when the default is a boolean * validating that nullable options have null as the default Trying to encode all of this in TARGET_* flags might be possible, but there are other attributes with different considerations that would need slightly different flags available, and eventually the set of flags would be too much. On the other hand, the current validation is entirely achieved by providing a reflection reference to what the attribute is applied to, which is what the RFC proposes. -Daniel

Tim Düsterhus

107 days ago
Hi Am 2026-05-16 16:22, schrieb Daniel Scherzer:
> […]
Okay, fair enough. I can see how running code for additional validation can be useful and just the targets themselves are insufficient. This would also make sense to align the capabilities of internal code and userland code. I would then suggest putting the extra validation where the existing validation is: Namely into the #[\Attribute] attribute. Closures and first class callables are supported in attributes since PHP 8.5 and would allow for a very straight-forward solution that keeps the attribute itself “pure metadata” in a value object. <?php #[Attribute] final class MyAttribute { public function __construct( public int $target, public Closure $validate, ) { } } #[MyAttribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION, MyNoDiscard::validate(...))] final class MyNoDiscard { public readonly ?string $message; public function __construct(?string $message = null) { } private static function validate(ReflectionMethod|ReflectionFunction $f) { if ($f instanceof ReflectionMethod) { if ($f->getName()[0] === '$') { throw new Error('May not on property hooks'); } } } } class X { public string $x { #[MyNoDiscard] get { return 'x'; } } } $r = new ReflectionClass(MyNoDiscard::class); $validator = $r->getAttributes()[0]->newInstance()->validate; $r = new ReflectionClass(X::class); $h = $r->getProperty('x')->getHook(PropertyHookType::Get); $validator($h); // Throws Best regards Tim Düsterhus

Larry Garfield

107 days ago
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. 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. 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(); } } } 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

Benjamin Eberlei

107 days ago
Am 16.05.2026, 21:20:51 schrieb Larry Garfield <larry@garfieldtech.com>:
> 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(); }

Larry Garfield

107 days ago
On Sat, May 16, 2026, at 8:30 PM, Benjamin Außenhofer wrote:
>> 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.
I have made that same point myself in the past many times; yet all the SA tools on the market right now still yell at me for writing to a readonly property from anywhere other than the constructor.
>> 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(); > }
I don't see what purpose that serves... What AttributeUtils does now is approximately this (again, simplified for clarity): public function analyze(string $class, string $attribute) { $rClass = new ReflectionClass($class); $classDef = $rClass->getAttributes($attribute, ReflectionAttribute::IS_INSTANCEOF)[0] ?? new $attribute(); if ($classDef instanceof FromReflectionClass) { $classDef->fromReflection($rClass); } if ($classDef instanceof ParseProperties) { // Use reflection to get attributes off the properties of the class. $classDef->setProperties($properties); } // And similar code for other components, hard coded. } Again, this does work, but it means you *must* go through AU's analyze() method or nothing works. I fully agree with the concerns that this is too modal/global. But so far, this seems the least bad way to address the issue of attributes being fundamentally dumb about their context. If someone has a better approach for handling that, please please do share it. I will also note the bonus feature in this RFC, the ReflectionAttributeTarget interface. I frankly want that even more than the reflection target. :-) Even if this RFC fails, that should be brought back in its own little RFC and needs to pass. --Larry Garfield

Tim Düsterhus

107 days ago
Hi Am 2026-05-17 05:35, schrieb Larry Garfield:
> But so far, this seems the least bad way to address the issue of > attributes being fundamentally dumb about their context.
I consider that to be a feature, not an issue. Context-specific logic hidden in a function is neither obvious to humans, nor to code. If I write `#[Field]` with `public function __construct(public ?string $name = null)`, then I expect `->name` to be `null`, not something else. And code handling Attributes in a generic way - for example some code generation - expects “pass all the arguments to the constructor” to do the right thing.
> I will also note the bonus feature in this RFC, the > ReflectionAttributeTarget interface. I frankly want that even more > than the reflection target. :-) Even if this RFC fails, that should be > brought back in its own little RFC and needs to pass.
The interface makes sense to me. I suppose we could probably ship that without an RFC. Best regards Tim Düsterhus

Удальцов Валентин

107 days ago
Daniel Scherzer <daniel.e.scherzer@gmail.com>:
> Hi internals, > > I'd like to start the discussion for a new RFC about adding a new method, > ReflectionAttribute::getCurrent(), to access the current reflection target > of an attribute. > > * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > * Implementation: https://github.com/php/php-src/pull/21440 > > Thanks, > -Daniel >
Hi, Daniel! Thank you for your proposal. I think the problem you are trying to solve arises from mixing two responsibilities in a single attribute class — it acts both as a data container and as a place for resolution logic. Separating these concerns makes the code cleaner and removes the need to access the reflection target from within the attribute itself. In my opinion, an attribute should be a simple DTO, and the resolution logic should belong elsewhere. Here's a small example of that: https://gist.github.com/vudaltsov/6dbab6a69967a55037fc7d6b13bd593b Here the messageClass can be inferred either from the attribute or from the function signature — the Metadata class has a non-nullable messageClass, and the attribute remains a plain DTO with an optional parameter. Of course, you can add a MetadataDriver interface and move my static methods to implementations, this will work especially well if you have multiple configuration sources (YAML, XML, attributes, PHP DSL, etc.). So, I believe that attributes should not be aware of the context they are declared in because the resolution responsibility does not belong to them.
-- Valentin

Larry Garfield

107 days ago
On Sun, May 17, 2026, at 9:05 AM, Valentin Udaltsov wrote:
> Daniel Scherzer <daniel.e.scherzer@gmail.com>: >> Hi internals, >> >> I'd like to start the discussion for a new RFC about adding a new method, ReflectionAttribute::getCurrent(), to access the current reflection target of an attribute. >> >> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >> * Implementation: https://github.com/php/php-src/pull/21440 >> >> Thanks, >> -Daniel > > Hi, Daniel! > > Thank you for your proposal. > > I think the problem you are trying to solve arises from mixing two > responsibilities in a single attribute class — it acts both as a data > container and as a place for resolution logic. Separating these > concerns makes the code cleaner and removes the need to access the > reflection target from within the attribute itself. > > In my opinion, an attribute should be a simple DTO, and the resolution > logic should belong elsewhere. Here's a small example of that: > https://gist.github.com/vudaltsov/6dbab6a69967a55037fc7d6b13bd593b Here > the messageClass can be inferred either from the attribute or from the > function signature — the Metadata class has a non-nullable > messageClass, and the attribute remains a plain DTO with an optional > parameter. Of course, you can add a MetadataDriver interface and move > my static methods to implementations, this will work especially well if > you have multiple configuration sources (YAML, XML, attributes, PHP > DSL, etc.). > > So, I believe that attributes should not be aware of the context they > are declared in because the resolution responsibility does not belong > to them. > > -- > Valentin
What you're saying implies that in the Serde examples I showed, you'd need *three* classes instead of one. 1. An attribute with the basic data. 2. A resolver object that uses data in the attribute and the reflection target to build some new value object. 3. The resulting value object. Which means that all three need different names, too. That is, quite frankly, grotesquely over-engineered (and I say that as someone with a penchant for over-engineering things) and highly confusing. Trying to set that up for a new use case is then substantially more work than it needs to be. --Larry Garfield

Удальцов Валентин

107 days ago
Larry Garfield <larry@garfieldtech.com>:
> On Sun, May 17, 2026, at 9:05 AM, Valentin Udaltsov wrote: > > Daniel Scherzer <daniel.e.scherzer@gmail.com>: > >> Hi internals, > >> > >> I'd like to start the discussion for a new RFC about adding a new > method, ReflectionAttribute::getCurrent(), to access the current reflection > target of an attribute. > >> > >> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > >> * Implementation: https://github.com/php/php-src/pull/21440 > >> > >> Thanks, > >> -Daniel > > > > Hi, Daniel! > > > > Thank you for your proposal. > > > > I think the problem you are trying to solve arises from mixing two > > responsibilities in a single attribute class — it acts both as a data > > container and as a place for resolution logic. Separating these > > concerns makes the code cleaner and removes the need to access the > > reflection target from within the attribute itself. > > > > In my opinion, an attribute should be a simple DTO, and the resolution > > logic should belong elsewhere. Here's a small example of that: > > https://gist.github.com/vudaltsov/6dbab6a69967a55037fc7d6b13bd593b Here > > the messageClass can be inferred either from the attribute or from the > > function signature — the Metadata class has a non-nullable > > messageClass, and the attribute remains a plain DTO with an optional > > parameter. Of course, you can add a MetadataDriver interface and move > > my static methods to implementations, this will work especially well if > > you have multiple configuration sources (YAML, XML, attributes, PHP > > DSL, etc.). > > > > So, I believe that attributes should not be aware of the context they > > are declared in because the resolution responsibility does not belong > > to them. > > > > -- > > Valentin > > What you're saying implies that in the Serde examples I showed, you'd need > *three* classes instead of one. > > 1. An attribute with the basic data. > 2. A resolver object that uses data in the attribute and the reflection > target to build some new value object. > 3. The resulting value object. > > Which means that all three need different names, too. That is, quite > frankly, grotesquely over-engineered (and I say that as someone with a > penchant for over-engineering things) and highly confusing. Trying to set > that up for a new use case is then substantially more work than it needs to > be. > > --Larry Garfield >
I gave an example with only two classes, because I don't like over-engineering either :) I think placing resolution logic in a static constructor of the resulting class is perfectly fine. Anyway, when you mix responsibilities to make code simpler, you should expect some trade-offs — that's OK. But I don't think the language should compensate for that, especially with a hack like this. There's another concern with the proposed approach: testing. Currently I don't need to test my attributes — they are simple structs. But with this RFC, logic can move into the primary constructor, and that logic needs unit tests. The only feasible way to test it is to place an attribute on a target and reflect it — which feels cumbersome to me.
-- Valentin

Daniel Scherzer

89 days ago
On Sun, May 17, 2026 at 7:06 AM Valentin Udaltsov < udaltsov.valentin@gmail.com> wrote:
> Daniel Scherzer <daniel.e.scherzer@gmail.com>: > >> Hi internals, >> >> I'd like to start the discussion for a new RFC about adding a new method, >> ReflectionAttribute::getCurrent(), to access the current reflection target >> of an attribute. >> >> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >> * Implementation: https://github.com/php/php-src/pull/21440 >> >> Thanks, >> -Daniel >> > > Hi, Daniel! > > Thank you for your proposal. > > I think the problem you are trying to solve arises from mixing two > responsibilities in a single attribute class — it acts both as a data > container and as a place for resolution logic. Separating these concerns > makes the code cleaner and removes the need to access the > reflection target from within the attribute itself. > > In my opinion, an attribute should be a simple DTO, and the resolution > logic should belong elsewhere. Here's a small example of that: > https://gist.github.com/vudaltsov/6dbab6a69967a55037fc7d6b13bd593b Here > the messageClass can be inferred either from the attribute or from the > function signature — the Metadata class has a non-nullable messageClass, > and the attribute remains a plain DTO with an optional parameter. Of > course, you can add a MetadataDriver interface and move my static methods > to implementations, this will work especially well if you have multiple > configuration sources (YAML, XML, attributes, PHP DSL, etc.). > > So, I believe that attributes should not be aware of the context they are > declared in because the resolution responsibility does not belong to them. > > -- > Valentin >
While separating the attribute data and the resolution is possible, in practice the resolution is frequently done in the attribute. This is already the case - my proposal just makes that resolution simpler. To be clear, code does not *need* to use the reflection access, and doing resolution outside of the attributes is (and will remain) perfectly acceptable. But given how much code already *does* do resolution within the attributes, this proposal would simplify that resolution code. See the AttributeUtils library for more context about how resolution is currently done in attributes. -Daniel

Daniel Scherzer

87 days ago
On Mon, May 4, 2026 at 1:24 PM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:
> Hi internals, > > I'd like to start the discussion for a new RFC about adding a new method, > ReflectionAttribute::getCurrent(), to access the current reflection target > of an attribute. > > * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent > * Implementation: https://github.com/php/php-src/pull/21440 > > Thanks, > -Daniel >
Barring any additional feedback, I plan to open the vote on this in the next few days. -Daniel

Benjamin Eberlei

86 days ago
Am 05.06.2026, 23:52:54 schrieb Daniel Scherzer <daniel.e.scherzer@gmail.com
>:
> On Mon, May 4, 2026 at 1:24 PM Daniel Scherzer < > daniel.e.scherzer@gmail.com> wrote: > >> Hi internals, >> >> I'd like to start the discussion for a new RFC about adding a new method, >> ReflectionAttribute::getCurrent(), to access the current reflection target >> of an attribute. >> >> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >> * Implementation: https://github.com/php/php-src/pull/21440 >> >> Thanks, >> -Daniel >> > > Barring any additional feedback, I plan to open the vote on this in the > next few days. > > -Daniel >
Hi Daniel, sorry i could not find the time to reply earlier. the idea in general is good, but i still very much disagree about the static method to get the reflector context. An attribute is often passed around as a configuration object or hierachy down to other services, for example in Symfony validator. #[NotNull] public $foo; $validator = new NotNullValidator(); $validator->validate($instance->foo, $notNullAttribute); For testing purposes it makes sense in these examples to construct the attributes directly using new SomeAttribute(). Now with this proposal, you need to be careful and know from the outside, when that is possible, or when you need to call it through ReflectionAttribute::newInstance(). The coupling becomes very strong suddenly. When you create the attributes yourself and without calling newInstance(), then the method does not work at all: $class = $reflectionAttribute->getName(); new $class(…$this->getArguments())); // ReflectionAttribute::getCurrent() returns null in this ctor. This is something the RFC is not clear about fully. There is the definition of an „attribute constructor“, but a constructor of an Attribute can be called many ways. This proposal is about the ctor only when called through newInstance(). I propose instead that we extend the newInstance() factory method, to be more versatile, and use the benefit here that we already have a factory method where we can place this logic: <?php interface ReflectionAwareAttribute { public function targetsReflector(Reflector $reflector); } class ReflectionAttribute { public function newInstance() { $class = $this->getName(); $instance = new $class(...$this->getArguments()); if ($instance instanceof ReflectionAwareAttribute) { $instance->targetsReflector($this); } return $instance; } } This is much cleaner OOP wise. 1. Increases testability of attribute classes that are reflection aware, only need a Reflector instance, no need to have actual attribute example set up. 2. Uses the existing factory method to concentrate the construction logic into one place. 3. avoids static method call with unclear semantics on when „getCurrent()“ returns something. 4. if a user of attributes has to avoid newInstance() (for whatever reason), they can call targetsReflector() themselves. There is no way to „setCurrent()“ reflector however, hiding part of this new behavior in the engine, where it could be 100% user controlled. The only „downside" is that you cant make properties set in targetsReflector() readonly. But from a semantical point of view, the attribute constructor should not have „magic" access to the reflector just to satisfy some own desire to achieve more readonly/value objects. And a class with a constructor calling a static method to get state cannot be called „simple“ or „value object“ in my opinion anyways. Take: #[Entity(repositoryClass: UserRepository::class, table: ‚users’)] #[Column(type: Types::STRING, name: ‚foo‘, nullable: false)] This is just a different way of writing: new Entity(repositoryClass: UserRepository::class, table: ‚users’) new Column(type: Types::STRING, name: ‚foo‘, nullable: false) Basic understanding of this for any PHP developer makes population of the global ReflectionAttribute::getCurrent() seeem „magical“, something we should avoid. greetings Benjamin

Benjamin Eberlei

86 days ago
Am 07.06.2026, 06:29:17 schrieb Benjamin Außenhofer <kontakt@beberlei.de>:
> > > Am 05.06.2026, 23:52:54 schrieb Daniel Scherzer < > daniel.e.scherzer@gmail.com>: > >> On Mon, May 4, 2026 at 1:24 PM Daniel Scherzer < >> daniel.e.scherzer@gmail.com> wrote: >> >>> Hi internals, >>> >>> I'd like to start the discussion for a new RFC about adding a new >>> method, ReflectionAttribute::getCurrent(), to access the current reflection >>> target of an attribute. >>> >>> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >>> * Implementation: https://github.com/php/php-src/pull/21440 >>> >>> Thanks, >>> -Daniel >>> >> >> Barring any additional feedback, I plan to open the vote on this in the >> next few days. >> >> -Daniel >> > > Hi Daniel, > > sorry i could not find the time to reply earlier. the idea in general is > good, but i still very much disagree about the static method to get the > reflector context. > > An attribute is often passed around as a configuration object or hierachy > down to other services, for example in Symfony validator. > > #[NotNull] > public $foo; > > $validator = new NotNullValidator(); > $validator->validate($instance->foo, $notNullAttribute); > > For testing purposes it makes sense in these examples to construct the > attributes directly using new SomeAttribute(). Now with this proposal, you > need to be careful and know from the outside, when that is possible, or > when you need to call it through ReflectionAttribute::newInstance(). The > coupling becomes very strong suddenly. > > When you create the attributes yourself and without calling newInstance(), > then the method does not work at all: > > $class = $reflectionAttribute->getName(); > new $class(…$this->getArguments())); // ReflectionAttribute::getCurrent() > returns null in this ctor. > > This is something the RFC is not clear about fully. There is the > definition of an „attribute constructor“, but a constructor of an Attribute > can be called many ways. This proposal is about the ctor only when called > through newInstance(). > > I propose instead that we extend the newInstance() factory method, to be > more versatile, and use the benefit here that we already have a factory > method where we can place this logic: > > <?php > > interface ReflectionAwareAttribute > { > public function targetsReflector(Reflector $reflector); > } > > class ReflectionAttribute > { > public function newInstance() > { > $class = $this->getName(); > $instance = new $class(...$this->getArguments()); > > if ($instance instanceof ReflectionAwareAttribute) { > $instance->targetsReflector($this); > } > > return $instance; > } > } > > This is much cleaner OOP wise. > > > 1. Increases testability of attribute classes that are reflection > aware, only need a Reflector instance, no need to have actual attribute > example set up. > 2. Uses the existing factory method to concentrate the construction > logic into one place. > 3. avoids static method call with unclear semantics on when > „getCurrent()“ returns something. > 4. if a user of attributes has to avoid newInstance() (for whatever > reason), they can call targetsReflector() themselves. There is no way to > „setCurrent()“ reflector however, hiding part of this new behavior in the > engine, where it could be 100% user controlled. > >
Just came up with another benefit of my alternative proposal, it can be polyfilled much easier, which Symfony as a big attribute user is certainly interested in. A polyfill can ship the interface, and then at every callsite of newInstance() add another call: if (PHP_VERSION_ID <= 80600 && method_exists($attribute, 'targetsReflector‘ )) { $attribute->targetsReflector($reflector); }

Larry Garfield

86 days ago
On Sun, Jun 7, 2026, at 7:07 AM, Benjamin Außenhofer wrote:
> Am 07.06.2026, 06:29:17 schrieb Benjamin Außenhofer <kontakt@beberlei.de>: >> >> >> Am 05.06.2026, 23:52:54 schrieb Daniel Scherzer <daniel.e.scherzer@gmail.com>: >>> On Mon, May 4, 2026 at 1:24 PM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote: >>>> Hi internals, >>>> >>>> I'd like to start the discussion for a new RFC about adding a new method, ReflectionAttribute::getCurrent(), to access the current reflection target of an attribute. >>>> >>>> * RFC: https://wiki.php.net/rfc/reflectionattribute-getcurrent >>>> * Implementation: https://github.com/php/php-src/pull/21440 >>>> >>>> Thanks, >>>> -Daniel >>> >>> Barring any additional feedback, I plan to open the vote on this in the next few days. >>> >>> -Daniel >> >> Hi Daniel, >> >> sorry i could not find the time to reply earlier. the idea in general is good, but i still very much disagree about the static method to get the reflector context. >> >> An attribute is often passed around as a configuration object or hierachy down to other services, for example in Symfony validator. >> >> #[NotNull] >> public $foo; >> >> $validator = new NotNullValidator(); >> $validator->validate($instance->foo, $notNullAttribute); >> >> For testing purposes it makes sense in these examples to construct the attributes directly using new SomeAttribute(). Now with this proposal, you need to be careful and know from the outside, when that is possible, or when you need to call it through ReflectionAttribute::newInstance(). The coupling becomes very strong suddenly. >> >> When you create the attributes yourself and without calling newInstance(), then the method does not work at all: >> >> $class = $reflectionAttribute->getName(); >> new $class(…$this->getArguments())); // ReflectionAttribute::getCurrent() returns null in this ctor. >> >> This is something the RFC is not clear about fully. There is the definition of an „attribute constructor“, but a constructor of an Attribute can be called many ways. This proposal is about the ctor only when called through newInstance(). >> >> I propose instead that we extend the newInstance() factory method, to be more versatile, and use the benefit here that we already have a factory method where we can place this logic: >> >> <?php >> >> interface ReflectionAwareAttribute >> { >> public function targetsReflector(Reflector $reflector); >> } >> >> class ReflectionAttribute >> { >> public function newInstance() >> { >> $class = $this->getName(); >> $instance = new $class(...$this->getArguments()); >> >> if ($instance instanceof ReflectionAwareAttribute) { >> $instance->targetsReflector($this); >> } >> >> return $instance; >> } >> } >> >> This is much cleaner OOP wise.
That is essentially what AttributeUtils does now. If you tag your class attribute with the FromReflectionClass, then the reflection object is passed to the fromReflection() method right after the constructor. I forget why people objected to that approach when this was discussed last year, but there were some who weren't fans. Personally, I'd be OK with that alternative. It would still let me greatly simplify the AttributeUtils API and reduce the number of moving parts. The catch, however, is that the type of the parameter becomes an issue. Reflector is valid, but too generic. This RFC proposes a new interface for reflection objects that can have attributes, which is good, but wouldn't add much here. What we really want is... generics. :-) interface ReflectionAware<T: Reflector> { ... } #[Attribute(Attribute::TARGET_CLASS|Attribute::TARGET_METHOD)] class MyAttrib implements ReflectionAware<ReflectionClass|ReflectionMethod> { // ... } --Larry Garfield