====== PHP RFC: Add ReflectionAttribute::getCurrent() ====== * Version: 0.3 * Date: 2026-04-19 * Author: Daniel Scherzer, daniel.e.scherzer@gmail.com * Status: Under discussion * Implementation: https://github.com/php/php-src/pull/21440 * Discussion thread: https://news-web.php.net/php.internals/130766 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 two new methods: * ReflectionAttribute::getReflectionTarget() allows identifying the target of an attribute represented by a ReflectionAttribute instance * the static method ReflectionAttribute::getCurrent(), //when called from an attribute constructor//, returns the ReflectionAttribute corresponding to the attribute usage Together, these methods allow an attribute constructor to identify the target it is 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 [[rfc:attributes_v2]] in PHP 8.0, with the current syntax adopted [[rfc: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 [[https://github.com/Crell/AttributeUtils|Crell/AttributeUtils]] composer library, which provides interfaces (e.g. FromReflectionClass) that indicate an attribute wants to be told what item (e.g. for FromReflectionClass, what class) the attribute was applied to. This information is provided after the attribute is constructed, and only when the attribute is loaded through the library. * adding manual ::tryFrom static constructors to use (e.g. Symfony/console's attributes like [[https://github.com/symfony/console/blob/ad665e88b844414e81707b0a33dfe757c65393f7/Attribute/Option.php#L60|Option]]) However, these rely on using an external library or creating attributes separately from ReflectionAttribute::newInstance(). This RFC proposes two new methods; the first, ReflectionAttribute::getReflectionTarget(), allows associating a ReflectionAttribute with the specific target it was applied to, rather than just the type of target. The second, ReflectionAttribute::getCurrent(), abstracts away the backtrace hacking. These would allow attributes to add custom validation or otherwise make use of the information about what item they were applied to: getReflectionTarget(); } } #[Demo("class")] class WithDemo { } $case = new ReflectionClass(WithDemo::class); echo $case; echo "\n"; $case->getAttributes()[0]->newInstance(); ?> ===== Proposal ===== When called within the constructor of an attribute object, ReflectionAttribute::getCurrent() will return a ReflectionAttribute that corresponds to the attribute application. Anywhere that a ReflectionAttribute object is available (now including attribute constructors), ReflectionAttribute::getReflectionTarget() returns the reflection object of the item to which the attribute was attached. The type of that reflection object is highly variable, as there are many types of item to which an attribute may be attached, and there is not a 1:1 correspondence between the Attribute::TARGET_* constant used and the resulting reflection object type. For example, a Attribute::TARGET_CLASS_CONSTANT attribute could be applied to a class constant, unit enum case, or backed enum case, which would result in ReflectionClassConstant, ReflectionEnumUnitCase, or ReflectionEnumBackedCase, respectively. To avoid a huge union return type, and to account for potential future expansions of attribute targets (e.g. [[rfc:attributes-on-constants]]) or new reflection subclasses, this RFC introduces a new interface, ReflectionAttributeTarget, which 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) When using the result of ReflectionAttribute::getReflectionTarget(), the developer may either assert() the type of the reflection object and/or use an instanceof check, as appropriate, if the logic depends on the type, or to provide information to static analysis tools and IDEs. For example, if an attribute may be placed on a class or method, it could check that like so: #[Attribute(Attribute::TARGET_CLASS)] class SingleTargetAttribute { public function __construct() { // SA tools won't know what type $target is, // beyond ReflectionAttributeTarget $target = ReflectionAttribute::getCurrent()->getReflectionTarget(); assert($target instanceof ReflectionClass); // Now SA tools know that $target is ReflectionClass, and can // offer appropriate code completion and validation. // More stuff here. } } #[Attribute(Attribute::TARGET_CLASS|Attribute::TARGET_METHOD)] class MultiUseAttribute { public function __construct() { $target = ReflectionAttribute::getCurrent()->getReflectionTarget(); if ($target instanceof ReflectionClass) { // Do class-specific logic here. } else if ($target instanceof ReflectionMethod) { // Do method-specific logic here. } // Do logic common to all attributes here. } } ==== Examples ==== Simple example: getReflectionTarget(); // For SA tools: 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 [[https://github.com/olvlvl/composer-attribute-collector|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. ==== Returned classes ==== ReflectionAttribute::getReflectionTarget() is documented on a type level to return ReflectionAttributeTarget, but the actual underlying class will be: ^ 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 * ReflectionEnum is a subclass of ReflectionClass * ReflectionEnumBackedCase is a subclass of ReflectionEnumUnitCase which is a subclass of ReflectionClassConstant To be clear, even when the ReflectionAttribute was retrieved from a different type, the return type will be the type listed in the table above. For example, using ReflectionClassConstant with an enum case works fine, but ReflectionAttribute::getReflectionTarget() will return the enum-specific subclass. Similarly, ReflectionObject can be used for classes and enums, but ReflectionClass or ReflectionEnum will be returned from ReflectionAttribute::getReflectionTarget() ===== 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 ===== None ===== Voting Choices ===== Add ReflectionAttribute::getCurrent(), ReflectionAttribute::getReflectionTarget() and the associated ReflectionAttributeTarget interface? ---- Primary Vote requiring a 2/3 majority to accept the RFC: * Yes * No * Abstain ===== Patches and Tests ===== https://github.com/php/php-src/pull/21440 ===== 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 ===== * [[rfc:attributes_v2]] * [[rfc:shorter_attribute_syntax_change]] * [[rfc:attributes-on-constants]] * https://github.com/Crell/AttributeUtils * https://github.com/olvlvl/composer-attribute-collector * Symfony/console (https://github.com/symfony/console/tree/ad665e88b844414e81707b0a33dfe757c65393f7) * 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 * v0.2: updates with improvements from Crell * v0.3: switch from having just a static method, ReflectionAttribute::getCurrent(): ReflectionAttributeTarget, to a static method, ReflectionAttribute::getCurrent(): ReflectionAttribute and an instance method ReflectionAttribute::getReflectionTarget(): ReflectionAttributeTarget