rfc:reflectionattribute-getcurrent

PHP RFC: Add ReflectionAttribute::getCurrent()

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. for FromReflectionClass, what class) the attribute was applied to. This information is provided after the attribute is constructed, and only when the library.
  • adding manual ::tryFrom static 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

  • ReflectionEnum is a subclass of ReflectionClass
  • ReflectionEnumBackedCase is a subclass of ReflectionEnumUnitCase which is a subclass of ReflectionClassConstant

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:

Add ReflectionAttribute::getCurrent() as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

Patches and Tests

Implementation

After the RFC is implemented, this section should contain:

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature

References

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
rfc/reflectionattribute-getcurrent.txt · Last modified: by daniels