AttributeUtils
- Driven entirely by interfaces
- All enhancements are opt-in
- Only analyze function or class, but class can scan-down
- Accepts class, anon class, object, function, or closure
- Cache friendly!
FromReflection*
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class Widget implements FromReflectionClass { |
| public readonly string $name; |
| |
| public function fromReflection(\ReflectionClass $subject): void { |
| $this->name ??= $subject->getShortName(); |
| } |
| } |
| |
| #[Widget] |
| class Dohicky { ... } |
| $analyzer = new \Crell\AttributeUtils\ClassAnalyzer(); |
| |
| $def = $analyzer->analyze(Dohicky::class, Widget::class); |
| |
| $def->name; |
Also FromReflectionMethod
, FromReflectionFunction
, FromReflectionProperty
, FromReflectionParameter
, FromReflectionClassConstant
ParseProperties
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class Data implements ParseProperties { |
| public readonly array $props; |
| |
| public function propertyAttribute(): string { return MyProperty::class; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function setProperties(array $ps): void { $this->props = $ps; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty { |
| public function __construct( |
| public readonly string $column = '', |
| ) {} |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class Data implements ParseProperties { |
| public readonly array $props; |
| |
| public function propertyAttribute(): string { return MyProperty::class; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function setProperties(array $ps): void { $this->props = $ps; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty { |
| public function __construct( |
| public readonly string $column = '', |
| ) {} |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class Data implements ParseProperties { |
| public readonly array $props; |
| |
| public function propertyAttribute(): string { return MyProperty::class; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function setProperties(array $ps): void { $this->props = $ps; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty { |
| public function __construct( |
| public readonly string $column = '', |
| ) {} |
| } |
| #[Data] |
| class Record { |
| #[MyProperty(column: 'beep')] |
| protected property $foo; |
| |
| private property $bar; |
| } |
| $dataAttrib = $analyzer->analyze(Record::class, Data::class); |
| |
| |
| |
Parse*
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties, ParseStaticProperites, |
| ParseMethods, ParseStaticMethods, ParseClassConstants {} |
| |
| #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] |
| class MyConstant {} |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty {} |
| |
| #[\Attribute(\Attribute::TARGET_METHOD)] |
| class MyMethod implements ParseParameters {} |
| |
| #[\Attribute(\Attribute::TARGET_PARAMETER)] |
| class MyParam {} |
| |
| #[MyClass] |
| class Record { |
| #[MyConstant] |
| public const AConstant = 'value'; |
| #[MyProperty] |
| protected string $foo; |
| #[MyMethod] |
| public function run(#[MyParam] string $key): string { ... } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties, ParseStaticProperites, |
| ParseMethods, ParseStaticMethods, ParseClassConstants {} |
| |
| #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] |
| class MyConstant {} |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty {} |
| |
| #[\Attribute(\Attribute::TARGET_METHOD)] |
| class MyMethod implements ParseParameters {} |
| |
| #[\Attribute(\Attribute::TARGET_PARAMETER)] |
| class MyParam {} |
| |
| #[MyClass] |
| class Record { |
| #[MyConstant] |
| public const AConstant = 'value'; |
| #[MyProperty] |
| protected string $foo; |
| #[MyMethod] |
| public function run(#[MyParam] string $key): string { ... } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties, ParseStaticProperites, |
| ParseMethods, ParseStaticMethods, ParseClassConstants {} |
| |
| #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] |
| class MyConstant {} |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty {} |
| |
| #[\Attribute(\Attribute::TARGET_METHOD)] |
| class MyMethod implements ParseParameters {} |
| |
| #[\Attribute(\Attribute::TARGET_PARAMETER)] |
| class MyParam {} |
| |
| #[MyClass] |
| class Record { |
| #[MyConstant] |
| public const AConstant = 'value'; |
| #[MyProperty] |
| protected string $foo; |
| #[MyMethod] |
| public function run(#[MyParam] string $key): string { ... } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties, ParseStaticProperites, |
| ParseMethods, ParseStaticMethods, ParseClassConstants {} |
| |
| #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] |
| class MyConstant {} |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty {} |
| |
| #[\Attribute(\Attribute::TARGET_METHOD)] |
| class MyMethod implements ParseParameters {} |
| |
| #[\Attribute(\Attribute::TARGET_PARAMETER)] |
| class MyParam {} |
| |
| #[MyClass] |
| class Record { |
| #[MyConstant] |
| public const AConstant = 'value'; |
| #[MyProperty] |
| protected string $foo; |
| #[MyMethod] |
| public function run(#[MyParam] string $key): string { ... } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties, ParseStaticProperites, |
| ParseMethods, ParseStaticMethods, ParseClassConstants {} |
| |
| #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] |
| class MyConstant {} |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty {} |
| |
| #[\Attribute(\Attribute::TARGET_METHOD)] |
| class MyMethod implements ParseParameters {} |
| |
| #[\Attribute(\Attribute::TARGET_PARAMETER)] |
| class MyParam {} |
| |
| #[MyClass] |
| class Record { |
| #[MyConstant] |
| public const AConstant = 'value'; |
| #[MyProperty] |
| protected string $foo; |
| #[MyMethod] |
| public function run(#[MyParam] string $key): string { ... } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties, ParseStaticProperites, |
| ParseMethods, ParseStaticMethods, ParseClassConstants {} |
| |
| #[\Attribute(\Attribute::TARGET_CLASS_CONSTANT)] |
| class MyConstant {} |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty {} |
| |
| #[\Attribute(\Attribute::TARGET_METHOD)] |
| class MyMethod implements ParseParameters {} |
| |
| #[\Attribute(\Attribute::TARGET_PARAMETER)] |
| class MyParam {} |
| |
| #[MyClass] |
| class Record { |
| #[MyConstant] |
| public const AConstant = 'value'; |
| #[MyProperty] |
| protected string $foo; |
| #[MyMethod] |
| public function run(#[MyParam] string $key): string { ... } |
| } |
ReadsClass
For handling defaults
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties { |
| public function __construct(public readonly ?string $color = 'red') {} |
| |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty implements ReadsClass { |
| public readonly string $color; |
| |
| public function __construct(?string $color = null) { |
| if ($color) $this->color = $color; |
| } |
| public function fromClassAttribute(MyClass $classDef): void { |
| $this->color ??= $classDef->color; |
| } |
| } |
| |
| #[MyClass(color: 'green')] |
| class Record { |
| #[MyProperty] |
| protected string $first; |
| #[MyProperty(color: 'blue')] |
| protected string $first; |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties { |
| public function __construct(public readonly ?string $color = 'red') {} |
| |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty implements ReadsClass { |
| public readonly string $color; |
| |
| public function __construct(?string $color = null) { |
| if ($color) $this->color = $color; |
| } |
| public function fromClassAttribute(MyClass $classDef): void { |
| $this->color ??= $classDef->color; |
| } |
| } |
| |
| #[MyClass(color: 'green')] |
| class Record { |
| #[MyProperty] |
| protected string $first; |
| #[MyProperty(color: 'blue')] |
| protected string $first; |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties { |
| public function __construct(public readonly ?string $color = 'red') {} |
| |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty implements ReadsClass { |
| public readonly string $color; |
| |
| public function __construct(?string $color = null) { |
| if ($color) $this->color = $color; |
| } |
| public function fromClassAttribute(MyClass $classDef): void { |
| $this->color ??= $classDef->color; |
| } |
| } |
| |
| #[MyClass(color: 'green')] |
| class Record { |
| #[MyProperty] |
| protected string $first; |
| #[MyProperty(color: 'blue')] |
| protected string $first; |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties { |
| public function __construct(public readonly ?string $color = 'red') {} |
| |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty implements ReadsClass { |
| public readonly string $color; |
| |
| public function __construct(?string $color = null) { |
| if ($color) $this->color = $color; |
| } |
| public function fromClassAttribute(MyClass $classDef): void { |
| $this->color ??= $classDef->color; |
| } |
| } |
| |
| #[MyClass(color: 'green')] |
| class Record { |
| #[MyProperty] |
| protected string $first; |
| #[MyProperty(color: 'blue')] |
| protected string $first; |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class MyClass implements ParseProperties { |
| public function __construct(public readonly ?string $color = 'red') {} |
| |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class MyProperty implements ReadsClass { |
| public readonly string $color; |
| |
| public function __construct(?string $color = null) { |
| if ($color) $this->color = $color; |
| } |
| public function fromClassAttribute(MyClass $classDef): void { |
| $this->color ??= $classDef->color; |
| } |
| } |
| |
| #[MyClass(color: 'green')] |
| class Record { |
| #[MyProperty] |
| protected string $first; |
| #[MyProperty(color: 'blue')] |
| protected string $first; |
| } |
Inheritance is opt-in
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class Person implements Inheritable { |
| public function __construct(public string $name = '') {} |
| } |
| |
| #[Person(name: 'Jorge')] |
| class A {} |
| class B extends A {} |
| |
| $attrib = $analyzer->analyze(B::class, Person::class); |
| print $attrib->name . PHP_EOL; |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| class Manager extends Person { |
| public function __construct(string $name = '', public string $dept) { |
| parent::_construct($name); |
| } |
| } |
| |
| #[Manager(name: 'Jorge', dept: 'Accounting')] |
| class A {} |
| |
| $attrib = $analyzer->analyze(A::class, Person::class); |
| print $attrib->name . PHP_EOL; |
| print $attrib->dept . PHP_EOL; |
Analyzer does instanceof
, so child attributes work, too
Sub-attributes
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public int $age; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Age::class => 'fromAge']; |
| } |
| |
| public function fromAge(?Age $sub): void { |
| |
| $this->age = $sub?->age ?? 0; |
| } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Age { |
| public function __construct(public int $age = 0) {} |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public int $age; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Age::class => 'fromAge']; |
| } |
| |
| public function fromAge(?Age $sub): void { |
| |
| $this->age = $sub?->age ?? 0; |
| } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Age { |
| public function __construct(public int $age = 0) {} |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public int $age; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Age::class => 'fromAge']; |
| } |
| |
| public function fromAge(?Age $sub): void { |
| |
| $this->age = $sub?->age ?? 0; |
| } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Age { |
| public function __construct(public int $age = 0) {} |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public int $age; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Age::class => 'fromAge']; |
| } |
| |
| public function fromAge(?Age $sub): void { |
| |
| $this->age = $sub?->age ?? 0; |
| } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Age { |
| public function __construct(public int $age = 0) {} |
| } |
Sub-attributes
| #[Person(name: 'Larry'), Age(21)] |
| class A {} |
| |
| $attribA = $analyzer->analyze(A::class, Person::class); |
| print "$attribA->name, $attribA->age\n"; |
| |
| class B {} |
| |
| $attribB = $analyzer->analyze(B::class, Person::class); |
| print "$attribB->name, $attribB->age\n"; |
| |
| #[Person(name: 'Larry')] |
| class C {} |
| |
| $attribC = $analyzer->analyze(C::class, Person::class); |
| print "$attribC->name, $attribC->age\n"; |
| #[Person(name: 'Larry'), Age(21)] |
| class A {} |
| |
| $attribA = $analyzer->analyze(A::class, Person::class); |
| print "$attribA->name, $attribA->age\n"; |
| |
| class B {} |
| |
| $attribB = $analyzer->analyze(B::class, Person::class); |
| print "$attribB->name, $attribB->age\n"; |
| |
| #[Person(name: 'Larry')] |
| class C {} |
| |
| $attribC = $analyzer->analyze(C::class, Person::class); |
| print "$attribC->name, $attribC->age\n"; |
| #[Person(name: 'Larry'), Age(21)] |
| class A {} |
| |
| $attribA = $analyzer->analyze(A::class, Person::class); |
| print "$attribA->name, $attribA->age\n"; |
| |
| class B {} |
| |
| $attribB = $analyzer->analyze(B::class, Person::class); |
| print "$attribB->name, $attribB->age\n"; |
| |
| #[Person(name: 'Larry')] |
| class C {} |
| |
| $attribC = $analyzer->analyze(C::class, Person::class); |
| print "$attribC->name, $attribC->age\n"; |
| #[Person(name: 'Larry'), Age(21)] |
| class A {} |
| |
| $attribA = $analyzer->analyze(A::class, Person::class); |
| print "$attribA->name, $attribA->age\n"; |
| |
| class B {} |
| |
| $attribB = $analyzer->analyze(B::class, Person::class); |
| print "$attribB->name, $attribB->age\n"; |
| |
| #[Person(name: 'Larry')] |
| class C {} |
| |
| $attribC = $analyzer->analyze(C::class, Person::class); |
| print "$attribC->name, $attribC->age\n"; |
Exclusions
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class Field implements Excludable { |
| public function __construct( |
| public readonly bool $exclude = false, |
| ) {} |
| |
| public function exclude(): bool { |
| return $this->exclude; |
| } |
| } |
| |
| #[ClassSettings(includeByDefault: true)] |
| class User { |
| public function __construct( |
| private string $loginName, |
| private string $displayName, |
| #[Field(exclude: true), SensitiveParameter] |
| private string $password, |
| ) {} |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class Field implements Excludable { |
| public function __construct( |
| public readonly bool $exclude = false, |
| ) {} |
| |
| public function exclude(): bool { |
| return $this->exclude; |
| } |
| } |
| |
| #[ClassSettings(includeByDefault: true)] |
| class User { |
| public function __construct( |
| private string $loginName, |
| private string $displayName, |
| #[Field(exclude: true), SensitiveParameter] |
| private string $password, |
| ) {} |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY)] |
| class Field implements Excludable { |
| public function __construct( |
| public readonly bool $exclude = false, |
| ) {} |
| |
| public function exclude(): bool { |
| return $this->exclude; |
| } |
| } |
| |
| #[ClassSettings(includeByDefault: true)] |
| class User { |
| public function __construct( |
| private string $loginName, |
| private string $displayName, |
| #[Field(exclude: true), SensitiveParameter] |
| private string $password, |
| ) {} |
| } |
Multi-value sub-attributes
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public array $knows; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Knows::class => 'fromKnows']; |
| } |
| |
| public function fromKnows(array $knows): void { $this->knows = $knows; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] |
| readonly class Knows implements Multivalue { |
| public function __construct(public string $name) {} |
| } |
| |
| #[Person(name: 'Larry')] |
| #[Knows('Kai')] |
| #[Knows('Molly')] |
| class A {} |
| |
| class B {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public array $knows; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Knows::class => 'fromKnows']; |
| } |
| |
| public function fromKnows(array $knows): void { $this->knows = $knows; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] |
| readonly class Knows implements Multivalue { |
| public function __construct(public string $name) {} |
| } |
| |
| #[Person(name: 'Larry')] |
| #[Knows('Kai')] |
| #[Knows('Molly')] |
| class A {} |
| |
| class B {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public array $knows; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Knows::class => 'fromKnows']; |
| } |
| |
| public function fromKnows(array $knows): void { $this->knows = $knows; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] |
| readonly class Knows implements Multivalue { |
| public function __construct(public string $name) {} |
| } |
| |
| #[Person(name: 'Larry')] |
| #[Knows('Kai')] |
| #[Knows('Molly')] |
| class A {} |
| |
| class B {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public array $knows; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Knows::class => 'fromKnows']; |
| } |
| |
| public function fromKnows(array $knows): void { $this->knows = $knows; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] |
| readonly class Knows implements Multivalue { |
| public function __construct(public string $name) {} |
| } |
| |
| #[Person(name: 'Larry')] |
| #[Knows('Kai')] |
| #[Knows('Molly')] |
| class A {} |
| |
| class B {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public array $knows; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Knows::class => 'fromKnows']; |
| } |
| |
| public function fromKnows(array $knows): void { $this->knows = $knows; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] |
| readonly class Knows implements Multivalue { |
| public function __construct(public string $name) {} |
| } |
| |
| #[Person(name: 'Larry')] |
| #[Knows('Kai')] |
| #[Knows('Molly')] |
| class A {} |
| |
| class B {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Person implements HasSubAttributes { |
| public array $knows; |
| |
| public function __construct(public string name = 'none') {} |
| |
| public function subAttributes(): array { |
| return [Knows::class => 'fromKnows']; |
| } |
| |
| public function fromKnows(array $knows): void { $this->knows = $knows; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] |
| readonly class Knows implements Multivalue { |
| public function __construct(public string $name) {} |
| } |
| |
| #[Person(name: 'Larry')] |
| #[Knows('Kai')] |
| #[Knows('Molly')] |
| class A {} |
| |
| class B {} |
Scopes
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Labeled implements ParseProperties { |
| public array $properties; |
| |
| public function setProperties(array $ps): void { $this->properties ??= $ps; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function propertyAttribute(): string { return Label::class; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] |
| readonly class Label implements SupportsScopes, Excludable { |
| public function __construct( |
| public string $name = 'None', |
| public ?string $language = null, |
| public bool $exclude = false, |
| ) {} |
| |
| public function scopes(): array { |
| return [$this->language]; |
| } |
| |
| public function exclude(): bool { return $this->exclude; } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Labeled implements ParseProperties { |
| public array $properties; |
| |
| public function setProperties(array $ps): void { $this->properties ??= $ps; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function propertyAttribute(): string { return Label::class; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] |
| readonly class Label implements SupportsScopes, Excludable { |
| public function __construct( |
| public string $name = 'None', |
| public ?string $language = null, |
| public bool $exclude = false, |
| ) {} |
| |
| public function scopes(): array { |
| return [$this->language]; |
| } |
| |
| public function exclude(): bool { return $this->exclude; } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Labeled implements ParseProperties { |
| public array $properties; |
| |
| public function setProperties(array $ps): void { $this->properties ??= $ps; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function propertyAttribute(): string { return Label::class; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] |
| readonly class Label implements SupportsScopes, Excludable { |
| public function __construct( |
| public string $name = 'None', |
| public ?string $language = null, |
| public bool $exclude = false, |
| ) {} |
| |
| public function scopes(): array { |
| return [$this->language]; |
| } |
| |
| public function exclude(): bool { return $this->exclude; } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Labeled implements ParseProperties { |
| public array $properties; |
| |
| public function setProperties(array $ps): void { $this->properties ??= $ps; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function propertyAttribute(): string { return Label::class; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] |
| readonly class Label implements SupportsScopes, Excludable { |
| public function __construct( |
| public string $name = 'None', |
| public ?string $language = null, |
| public bool $exclude = false, |
| ) {} |
| |
| public function scopes(): array { |
| return [$this->language]; |
| } |
| |
| public function exclude(): bool { return $this->exclude; } |
| } |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Labeled implements ParseProperties { |
| public array $properties; |
| |
| public function setProperties(array $ps): void { $this->properties ??= $ps; } |
| public function includePropertiesByDefault(): bool { return true; } |
| public function propertyAttribute(): string { return Label::class; } |
| } |
| |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] |
| readonly class Label implements SupportsScopes, Excludable { |
| public function __construct( |
| public string $name = 'None', |
| public ?string $language = null, |
| public bool $exclude = false, |
| ) {} |
| |
| public function scopes(): array { |
| return [$this->language]; |
| } |
| |
| public function exclude(): bool { return $this->exclude; } |
| } |
Scopes
| class App { |
| #[Label(name: 'Installation')] |
| #[Label(name: 'Instalación', language: 'es')] |
| public string $install; |
| |
| #[Label(name: 'Setup')] |
| #[Label(name: 'Configurar', language: 'es')] |
| #[Label(name: 'Einrichten', language: 'de')] |
| public string $setup; |
| |
| #[Label(name: 'Einloggen', language: 'de')] |
| #[Label(language: 'fr', exclude: true)] |
| public string $login; |
| |
| public string $custom; |
| } |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['es']); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['fr']); |
| |
| class App { |
| #[Label(name: 'Installation')] |
| #[Label(name: 'Instalación', language: 'es')] |
| public string $install; |
| |
| #[Label(name: 'Setup')] |
| #[Label(name: 'Configurar', language: 'es')] |
| #[Label(name: 'Einrichten', language: 'de')] |
| public string $setup; |
| |
| #[Label(name: 'Einloggen', language: 'de')] |
| #[Label(language: 'fr', exclude: true)] |
| public string $login; |
| |
| public string $custom; |
| } |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['es']); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['fr']); |
| |
| class App { |
| #[Label(name: 'Installation')] |
| #[Label(name: 'Instalación', language: 'es')] |
| public string $install; |
| |
| #[Label(name: 'Setup')] |
| #[Label(name: 'Configurar', language: 'es')] |
| #[Label(name: 'Einrichten', language: 'de')] |
| public string $setup; |
| |
| #[Label(name: 'Einloggen', language: 'de')] |
| #[Label(language: 'fr', exclude: true)] |
| public string $login; |
| |
| public string $custom; |
| } |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['es']); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['fr']); |
| |
| class App { |
| #[Label(name: 'Installation')] |
| #[Label(name: 'Instalación', language: 'es')] |
| public string $install; |
| |
| #[Label(name: 'Setup')] |
| #[Label(name: 'Configurar', language: 'es')] |
| #[Label(name: 'Einrichten', language: 'de')] |
| public string $setup; |
| |
| #[Label(name: 'Einloggen', language: 'de')] |
| #[Label(language: 'fr', exclude: true)] |
| public string $login; |
| |
| public string $custom; |
| } |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['es']); |
| |
| |
| $labels = $analyzer->analyze(App::class, Labeled::class, scopes: ['fr']); |
| |
Transitivity
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Names implements ParseProperties { |
| |
| public function propertyAttribute(): string { return FancyName::class; } |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] |
| readonly class FancyName implements TransitiveProperty { |
| public function __construct(public string $name = '') {} |
| } |
| |
| class Stuff { |
| protected string $bare; |
| #[FancyName('A happy little integer')] |
| protected int $namedNumber; |
| #[FancyName('Her Majesty Queen Elizabeth II')] |
| protected Person $namedPerson; |
| protected Person $anonymousPerson; |
| } |
| |
| #[FancyName('I am not an object, I am a free man!')] |
| class Person {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Names implements ParseProperties { |
| |
| public function propertyAttribute(): string { return FancyName::class; } |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] |
| readonly class FancyName implements TransitiveProperty { |
| public function __construct(public string $name = '') {} |
| } |
| |
| class Stuff { |
| protected string $bare; |
| #[FancyName('A happy little integer')] |
| protected int $namedNumber; |
| #[FancyName('Her Majesty Queen Elizabeth II')] |
| protected Person $namedPerson; |
| protected Person $anonymousPerson; |
| } |
| |
| #[FancyName('I am not an object, I am a free man!')] |
| class Person {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Names implements ParseProperties { |
| |
| public function propertyAttribute(): string { return FancyName::class; } |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] |
| readonly class FancyName implements TransitiveProperty { |
| public function __construct(public string $name = '') {} |
| } |
| |
| class Stuff { |
| protected string $bare; |
| #[FancyName('A happy little integer')] |
| protected int $namedNumber; |
| #[FancyName('Her Majesty Queen Elizabeth II')] |
| protected Person $namedPerson; |
| protected Person $anonymousPerson; |
| } |
| |
| #[FancyName('I am not an object, I am a free man!')] |
| class Person {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Names implements ParseProperties { |
| |
| public function propertyAttribute(): string { return FancyName::class; } |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] |
| readonly class FancyName implements TransitiveProperty { |
| public function __construct(public string $name = '') {} |
| } |
| |
| class Stuff { |
| protected string $bare; |
| #[FancyName('A happy little integer')] |
| protected int $namedNumber; |
| #[FancyName('Her Majesty Queen Elizabeth II')] |
| protected Person $namedPerson; |
| protected Person $anonymousPerson; |
| } |
| |
| #[FancyName('I am not an object, I am a free man!')] |
| class Person {} |
| #[\Attribute(\Attribute::TARGET_CLASS)] |
| readonly class Names implements ParseProperties { |
| |
| public function propertyAttribute(): string { return FancyName::class; } |
| } |
| #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)] |
| readonly class FancyName implements TransitiveProperty { |
| public function __construct(public string $name = '') {} |
| } |
| |
| class Stuff { |
| protected string $bare; |
| #[FancyName('A happy little integer')] |
| protected int $namedNumber; |
| #[FancyName('Her Majesty Queen Elizabeth II')] |
| protected Person $namedPerson; |
| protected Person $anonymousPerson; |
| } |
| |
| #[FancyName('I am not an object, I am a free man!')] |
| class Person {} |
| $p = $analyzer->analyze(Stuff::class, Names::class)->properties; |
| |
| print $p['bare']->name; |
| print $p['namedNumber']->name; |
| print $p['namedPerson']->name; |
| print $p['anonymousPerson']->name; |
Finalizable
| class Info implements Finalizable, FromReflectionClass { |
| public readonly string $name; |
| |
| public function __construct(?string $name = null) { |
| if ($name) $this->name = $name; |
| } |
| |
| public function fromReflection(\ReflectionClass $r) { |
| if ($r->getNamespace() == '\\My\\Space') $this->name ??= $r->getShortName(); |
| } |
| |
| public function finalize() { |
| $this->name ??= 'Untitled'; |
| } |
| } |
| class Info implements Finalizable, FromReflectionClass { |
| public readonly string $name; |
| |
| public function __construct(?string $name = null) { |
| if ($name) $this->name = $name; |
| } |
| |
| public function fromReflection(\ReflectionClass $r) { |
| if ($r->getNamespace() == '\\My\\Space') $this->name ??= $r->getShortName(); |
| } |
| |
| public function finalize() { |
| $this->name ??= 'Untitled'; |
| } |
| } |
| class Info implements Finalizable, FromReflectionClass { |
| public readonly string $name; |
| |
| public function __construct(?string $name = null) { |
| if ($name) $this->name = $name; |
| } |
| |
| public function fromReflection(\ReflectionClass $r) { |
| if ($r->getNamespace() == '\\My\\Space') $this->name ??= $r->getShortName(); |
| } |
| |
| public function finalize() { |
| $this->name ??= 'Untitled'; |
| } |
| } |
| class Info implements Finalizable, FromReflectionClass { |
| public readonly string $name; |
| |
| public function __construct(?string $name = null) { |
| if ($name) $this->name = $name; |
| } |
| |
| public function fromReflection(\ReflectionClass $r) { |
| if ($r->getNamespace() == '\\My\\Space') $this->name ??= $r->getShortName(); |
| } |
| |
| public function finalize() { |
| $this->name ??= 'Untitled'; |
| } |
| } |
Functions
| use Crell\AttributeUtils\FuncAnalyzer; |
| |
| #[MyFunc] |
| function beep(int $a) {} |
| |
| $closure = #[MyClosure] fn(int $a) => $a + 1; |
| |
| |
| $analyzer = new FuncAnalyzer(); |
| $funcDef = $analyzer->analyze('beep', MyFunc::class); |
| |
| |
| $analyzer = new FuncAnalyzer(); |
| $funcDef = $analyzer->analyze($closure, MyFunc::class); |
ParseParameters
, Finalizable
, FromReflectionFunction
Example: Serde
| #[ClassSettings(renameWith: new Prefix('mail_')] |
| class MailConfig { |
| protected string $host = 'smtp.example.com'; |
| |
| protected int $port = 25; |
| |
| protected string $user = 'me'; |
| |
| #[Field(renameWith: Case::CAPITALIZE)] |
| protected string $password = 'sssh'; |
| |
| #[PostLoad] |
| private function validate(): void { |
| if ($port > 1024) throw new \InvalidArgumentException(); |
| } |
| } |
| #[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)] |
| class Field implements |
| FromReflectionProperty, |
| HasSubAttributes, |
| Excludable, |
| SupportsScopes, |
| ReadsClass, |
| Finalizable |
Example: Serde
| use Crell\Serde\Attributes\StaticTypeMap; |
| |
| #[StaticTypeMap(key: 'type', map: [ |
| 'paper' => PaperBook::class, |
| 'ebook' => DigitalBook::class, |
| ])] |
| interface Book {} |
| |
| class PaperBook implements Book {} |
| class DigitalBook implements Book {} |
| |
| class Sale { |
| protected Book $book; |
| protected float $discountRate; |
| } |
Example: Tukio
| class ListenerOne { |
| public function __construct( |
| private readonly DependencyA $depA, |
| ) {} |
| |
| public function __invoke(MyEvent $event): void { ... } |
| } |
| |
| #[ListenerBefore(ListenerOne::class)] |
| class ListenerTwo { |
| public function __invoke(MyEvent $event): void { ... } |
| } |
| |
| $provider->listenerService(ListenerOne::class); |
| $provider->listenerService(ListenerTwo::class); |
Example: Tukio
| class MyListeners { |
| #[Listener(id: 'a')] |
| public function onA(CollectingEvent $event): void { |
| $event->add('A'); |
| } |
| |
| #[ListenerPriority(priority: 5)] |
| public function onB(CollectingEvent $event): void { |
| $event->add('B'); |
| } |
| |
| #[ListenerAfter(after: 'a')] |
| public function onD(CollectingEvent $event): void { |
| $event->add('D'); |
| } |
| |
| #[ListenerPriority(priority: -5)] |
| public function notNormalName(CollectingEvent $event): void { |
| $event->add('F'); |
| } |
| |
| public function ignoredMethodThatDoesNothing(): void { |
| throw new \Exception('What are you doing here?'); |
| } |
| } |
Example: Routing (fictional)
| #[Routes(path: '/app/v1', perm: 'auth user')] |
| class ProductController { |
| #[Route] |
| public function index() { ... } |
| |
| #[Route(path: '/{$id}')] |
| public function get(string $id) { ... } |
| |
| #[Route(method: Http::Post, perm: 'create post')] |
| public function post(Product $p) { ... } |
| |
| #[Route(path: '/{$id}', method: Http::Delete, perm: 'delete')] |
| public function delete(string $id) { ... } |
| } |
Example: TYPO3 DI
| class SomeUserService { |
| public function __construct( |
| private readonly ADependency $dep, |
| #[Channel('user')] |
| private readonly LoggerInterface $logger, |
| ) {} |
| } |