Reputation: 55427
According to the RFC on Enumerations, attributes can be added to cases by using Attribute::TARGET_CLASS_CONSTANT
. (Actually, the RFC says TARGET_CLASS_CONST
but that is either a typo or a later change.) I'm having trouble trying to access them using Reflection, however.
Given this setup:
#[Attribute(Attribute::TARGET_CLASS_CONSTANT)]
class TestAttribute
{
public function __construct(public string $value)
{
}
}
enum TestNum
{
#[TestAttribute('alpha value')]
case ALPHA;
#[TestAttribute('beta value')]
case BETA;
}
I would expect the following code to give me an array with a single attribute, however it returns an empty array.
$obj = TestNum::ALPHA;
$reflection = new ReflectionClass($obj);
$classAttributes = $reflection->getAttributes(TestAttribute::class);
var_dump($classAttributes);
Demo here: https://3v4l.org/uLDVQ#v8.1.2
I found a test-case for this in the PHP src, however the usage isn't what I'd expect. Instead of using an instance, I need to decompose it:
var_dump((new \ReflectionClassConstant(TestNum::class, 'ALPHA'))->getAttributes(TestAttribute::class)[0]->newInstance());
Demo here: https://3v4l.org/BsA9r#v8.1.2
I can use that format, but it feels really hacky since I'm pretty much using reflection inside of reflection:
var_dump((new \ReflectionClassConstant($obj::class, $obj->name))->getAttributes(TestAttribute::class)[0]->newInstance());
Demo here: https://3v4l.org/YY6Oa#v8.1.2
Specifically, the new \ReflectionClassConstant($obj::class, $obj->name)
pattern seems strangely boilerplate.
Is there another way to access individual enumeration case attributes that I'm missing?
Upvotes: 2
Views: 1312
Reputation: 55427
Thanks both to lukas.j and Clément Baconnier for their comments and answers.
I think my problem is ultimately my mental model of the enum. In my mind, an enum is syntactic sugar around abstract classes and final sub-classes:
abstract class TestNum
{
// Not valid, just representative
const ALPHA = new Alpha();
}
final class Alpha extends TestNum{};
But in reality, enums are closer to:
final class TestNum
{
private $map;
// Not valid, just representative
const ALPHA = self::createInstance('ALPHA');
private function createInstance($key) : self
{
$map[$key] = new self();
return $map[$key];
}
}
If you look at my (incorrect) mental model, the use-case for attributes on an enum case are the same reasons for attributes on classes.
So the answer to my question is no. The fact that you pass TestNum::ALPHA
to reflection, either directly or through an instance, does not mean you are reflecting upon that instance, you are only ever reflecting on TestNum
. This means there's zero difference between these three:
new ReflectionClass(TestNum::class)
new ReflectionClass(TestNum::ALPHA)
new ReflectionClass(TestNum::ALPHA::class)
Yes, you can get ALPHA
by explicitly asking for it by a named-string, either as a literal, or through the name
property on an enum. But reflection doesn't "know" that TestNum::ALPHA
is the ALPHA
instance of TestNum
. To say that another way, there's no reflectable internal state to TestNum::ALPHA
to differentiate it from TestNum::BETA
.
Upvotes: 0
Reputation: 7163
#[Attribute( Attribute::TARGET_CLASS_CONSTANT )]
class TestAttribute {
public function __construct(public string $value) {
}
}
enum TestNum {
#[TestAttribute( 'alpha value' )]
case ALPHA;
#[TestAttribute( 'beta value' )]
case BETA;
}
$obj = TestNum::ALPHA;
$ref = new ReflectionEnumUnitCase($obj::class, $obj->name);
$argument = $ref->getAttributes('TestAttribute')[0]->getArguments()[0];
print_r($argument); // Prints: 'alpha value'
Upvotes: 4
Reputation: 7163
#[Attribute( Attribute::TARGET_CLASS_CONSTANT )]
class TestAttribute {
public function __construct(public string $value) {
}
}
enum TestNum {
#[TestAttribute( 'alpha value' )]
case ALPHA;
#[TestAttribute( 'beta value' )]
case BETA;
}
$obj = TestNum::ALPHA;
$ref = (new ReflectionClass($obj))->getReflectionConstant('ALPHA');
var_dump($ref->getAttributes()[0]->getArguments()); // alpha value
var_dump($ref->getAttributes()[0]->getName()); // TestAttribute
var_dump($ref->getName()); // ALPHA
var_dump($ref->getValue()); // enum(TestNum::ALPHA)
Upvotes: 3