PHP RFC: Add ReflectionAttribute::getCurrent()
- Version: 0.1
- Date: 2026-04-19
- Author: Daniel Scherzer, daniel.e.scherzer@gmail.com
- Status: Draft
- Implementation: https://github.com/php/php-src/pull/21440
Userland attributes (i.e. attributes not provided by PHP itself or an extension) are created with ReflectionAttribute::newInstance() and have their constructors called with the arguments provided to the attribute. However, they lack addition context as to what they are being applied to. While it is possible to hack around with debug_backtrace to identify the original ReflectionAttribute instance, and thus the type of construct the attribute is applied to (class, class constant, etc.) there is no way to identify which class (or class constant, etc.) the attribute is being applied to.
This RFC proposes the addition of 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.
For the purposes of this RFC, an “item” is something that can have attributes applied to it, including classes, functions, parameters, constants, etc.
Introduction
Attributes were first introduced in attributes_v2 in PHP 8.0, with the current syntax adopted shorter_attribute_syntax_change. Userland attributes provide a way to add metadata to items in a structured way that can be read by external tools using reflection.
However, userland attributes, when created via ReflectionAttribute::newInstance(), only know about the arguments passed to the attribute when it was applied. Backtracing hacking can be used to identify the ReflectionAttribute object that was used, and from there ReflectionAttribute::getTarget() could be used to identify what type of item the attribute was applied to, but not which specific item.
Various workarounds exist in userland, such as:
- the Crell/AttributeUtils composer library, which provides interfaces (e.g.
FromReflectionClass) that indicate an attribute wants to be told what item (e.g. forFromReflectionClass, what class) the attribute was applied to. This information is provided after the attribute is constructed, and only when the library. - adding manual
::tryFromstatic constructors to use (e.g. Symfony/console's attributes like Option)
However, these rely on using an external library or creating attributes separately from ReflectionAttribute::newInstance(). This RFC proposes a PHP-provided replacement, ReflectionAttribute::getCurrent(). This would allow attributes to add custom validation or otherwise make use of the information about what item they were applied to:
<?php #[Attribute] class Demo { public function __construct($args) { // will echo the same ReflectionClass that `echo $case;` does below // this is a contrived example, real world code would do something useful echo ReflectionAttribute::getCurrent(); } } #[Demo("class")] class WithDemo { } $case = new ReflectionClass(WithDemo::class); echo $case; echo "\n"; $case->getAttributes()[0]->newInstance(); ?>
Proposal
When called from the constructor of an attribute, the method will return one of the following, depending on what item the attribute was applied to. Attributes can be applied to different types of items depending on the Attribute::TARGET_* flags, and even among the same type of item the reflection extension sometimes has more specific classes to use
| Flag | Applied to | Return type |
|---|---|---|
Attribute::TARGET_CLASS | Class | ReflectionClass |
Attribute::TARGET_CLASS | Trait | ReflectionClass |
Attribute::TARGET_CLASS | Interface | ReflectionClass |
Attribute::TARGET_CLASS | Enum | ReflectionEnum |
Attribute::TARGET_METHOD | Class method | ReflectionMethod |
Attribute::TARGET_CLASS_CONSTANT | Class constant | ReflectionClassConstant |
Attribute::TARGET_CLASS_CONSTANT | Enum case (unit) | ReflectionEnumUnitCase |
Attribute::TARGET_CLASS_CONSTANT | Enum case (backed) | ReflectionEnumBackedCase |
Attribute::TARGET_PROPERTY | Class property | ReflectionProperty |
Attribute::TARGET_FUNCTION | Global function | ReflectionFunction |
Attribute::TARGET_PARAMETER | Function parameter | ReflectionParameter |
Attribute::TARGET_PARAMETER | Method parameter | ReflectionParameter |
Attribute::TARGET_CONSTANT | Global constant | ReflectionConstant |
Note that
ReflectionEnumis a subclass ofReflectionClassReflectionEnumBackedCaseis a subclass ofReflectionEnumUnitCasewhich is a subclass ofReflectionClassConstant
To avoid a huge union return type, and to account for potential future expansions of attribute targets (e.g. attributes-on-constants) or new reflection subclasses, the return type will be declared with a new interface, ReflectionAttributeTarget, that represents “things that can have attributes” or “things that an attribute can be applied to”.
Thus, the additions in this RFC look like the following (classes that implement Reflector did so previously, and are just updated to also implement ReflectionAttributeTarget)
<?php interface ReflectionAttributeTarget extends Reflector { public function getAttributes(?string $name = null, int $flags = 0): array {} } // ... abstract class ReflectionFunctionAbstract implements Reflector, ReflectionAttributeTarget { /* ... */ } class ReflectionClass implements Reflector, ReflectionAttributeTarget { /* ... */ } class ReflectionProperty implements Reflector, ReflectionAttributeTarget { /* ... */ } class ReflectionClassConstant implements Reflector, ReflectionAttributeTarget { /* ... */ } class ReflectionParameter implements Reflector, ReflectionAttributeTarget { /* ... */ } class ReflectionConstant implements Reflector, ReflectionAttributeTarget { /* ... */ } class ReflectionAttribute implements Reflector { // ... public static function getCurrent(): ReflectionAttributeTarget {} } ?>
Examples
Simple example:
<?php #[Attribute(Attribute::TARGET_CLASS)] class StableInterface { public function __construct() { $target = ReflectionAttribute::getCurrent(); // Only targets classes assert($target instanceof ReflectionClass); // Should only be applied to interfaces if (!$target->isInterface()) { throw new Exception("#[StableInterface] is only for interfaces!"); } // Some global function that exists elsewhere record_stable_interface($target->getName()); } } ?>
The above attribute, when instantiated, will confirm that it is being applied to an interface (rather than a class, trait, or enum) and then record the name of the targeted interface. This could then be used by tools to automatically generate a list of interfaces in a library that are considered stable, perhaps with the help of the olvlvl/composer-attribute-collector library.
ReflectionAttribute::getCurrent() must be called directly from the constructor of an attribute that is being instantiated with ReflectionAttribute::newInstance(), otherwise it fails.
Backward Incompatible Changes
None
Proposed PHP Version(s)
Next minor version (PHP 8.6).
RFC Impact
To the Ecosystem
Attribute-related libraries may want to make use of the new feature.
To Existing Extensions
Only ext/reflection is affected - all of this is implemented in reflection.
To SAPIs
None
Open Issues
Make sure there are no open issues when the vote starts!
Behavior on failure
When ReflectionAttribute::getCurrent() is not called from the constructor of an attribute being instantiated via ReflectionAttribute::newInstance(), should it throw an exception, or return null?
Future Scope
Now that ReflectionAttribute knows what it is being applied to, maybe there would be a way to retrieve that information directly from an instance of ReflectionAttribute
Voting Choices
Add ReflectionAttribute::getCurrent() and the associated ReflectionAttributeTarget interface?
Primary Vote requiring a 2/3 majority to accept the RFC:
Patches and Tests
Implementation
After the RFC is implemented, this section should contain:
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
References
- Previous mailing list discussion: https://externals.io/message/120443
Rejected Features
Keep this updated with features that were discussed on the mail lists.
Changelog
If there are major changes to the initial proposal, please include a short summary with a date or a link to the mailing list announcement here, as not everyone has access to the wikis' version history.
- v0.1: created