diff --git a/README.md b/README.md index ca7fa4f..4706d08 100644 --- a/README.md +++ b/README.md @@ -818,6 +818,54 @@ In this case, a class may be marked with either `Screen` or `Audio`, but not bot In this example, `$displayInfoA->type` will be an instance of `Screen`, `$displayInfoB->type` will be an instance of `Audio`, and `$displayInfoC->type` will be `null`. +### Interface using attributes + +Interfaces may also implement attributes. This is useful for cases in which you want to define a type map at the interface level. + +Note: in this case, make sure the attribute class implements the `Inheritable` interface, so that implementing classes can inherit it. + +```php +use Crell\AttributeUtils\Inheritable; + +#[Attribute(Attribute::TARGET_CLASS)] +final class Application implements Inheritable +{ + public function __construct( + public readonly string $name, + ) { + } +} + +#[Application(name: 'app')] +interface Something +{ +} + +enum MyEnum: string implements Something +{ + case A = 'a'; + case B = 'b'; +} + +#[Application(name: 'other-app')] +enum AnotherEnum: string implements Something +{ + case A = 'a'; + case B = 'b'; +} + + +$analyzer = new Crell\AttributeUtils\Analyzer(); + +/** @var Application $anotherEnumAttributes */ +$enumAttributes = $analyzer->analyze(MyEnum::class, Application::class); +print $enumAttributes->name . PHP_EOL; // Prints 'app' + +/** @var Application $classAttributes */ +$anotherEnumAttributes = $analyzer->analyze(AnotherEnum::class, Application::class); +print $anotherEnumAttributes->name . PHP_EOL; // Prints 'other-app' +``` + ## Change log Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. diff --git a/src/AttributeParser.php b/src/AttributeParser.php index b61356e..63436b5 100644 --- a/src/AttributeParser.php +++ b/src/AttributeParser.php @@ -224,8 +224,7 @@ protected function attributeInheritanceTree(\Reflector $subject, string $attribu \ReflectionMethod::class => $this->classElementInheritanceTree($subject), \ReflectionClassConstant::class => $this->classElementInheritanceTree($subject), \ReflectionParameter::class => $this->parameterInheritanceTree($subject), - // If it's an enum, there's nothing to inherit so just stub that out. - \ReflectionEnum::class => [], + \ReflectionEnum::class => $this->classInheritanceTree($subject), }; } } @@ -235,7 +234,7 @@ protected function attributeInheritanceTree(\Reflector $subject, string $attribu * * This includes both classes and interfaces. * - * @param \ReflectionClass $subject + * @param \ReflectionClass|\ReflectionEnum<\UnitEnum> $subject * The reflection of the class for which we want the ancestors. * @return iterable<\ReflectionClass> * @throws \ReflectionException diff --git a/tests/ClassAnalyzerTest.php b/tests/ClassAnalyzerTest.php index c1f3ce1..98e6a5a 100644 --- a/tests/ClassAnalyzerTest.php +++ b/tests/ClassAnalyzerTest.php @@ -49,6 +49,7 @@ use Crell\AttributeUtils\Records\ClassWithScopesMulti; use Crell\AttributeUtils\Records\ClassWithScopesNotDefault; use Crell\AttributeUtils\Records\ClassWithSubAttributes; +use Crell\AttributeUtils\Records\EnumWithInterface; use Crell\AttributeUtils\Records\LabeledApp; use Crell\AttributeUtils\Records\MissingPropertyAttributeArguments; use Crell\AttributeUtils\Records\MultiuseClass; @@ -416,6 +417,15 @@ public static function attributeTestProvider(): \Generator }, ]; + yield 'Enum implementing interface' => [ + 'subject' => EnumWithInterface::class, + 'attribute' => BasicClass::class, + 'test' => static function(mixed $classDef) { + self::assertEquals(5, $classDef->a); + self::assertEquals(10, $classDef->b); + }, + ]; + yield 'Field takes defaults from class' => [ 'subject' => PropertyThatTakesClassDefault::class, 'attribute' => PropertyTakesClassDefaultClass::class, diff --git a/tests/Records/EnumWithInterface.php b/tests/Records/EnumWithInterface.php new file mode 100644 index 0000000..f4bbf43 --- /dev/null +++ b/tests/Records/EnumWithInterface.php @@ -0,0 +1,11 @@ +