Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Attributes

An attribute adds typed data to a file, declaration, or parameter. Whim checks the attribute class, its target, its arguments, and whether it may repeat.

Defining an attribute

Mark a class with Whim\Attribute\Attribute:

use Whim\Attribute\Attribute;

#[Attribute(Attribute::TARGET_CLASS)]
final readonly class Table {
  public function __construct(public string $name) {}
}

#[Table('users')]
final class User {}

Applying #[Table('users')] stores the class and arguments. Reflection’s newInstance() creates a Table value. The arguments follow the same count and type rules as a normal constructor call and may not read local variables.

An attribute class may have no constructor. Apply it without parentheses:

use Whim\Attribute\Attribute;

#[Attribute(Attribute::TARGET_FUNCTION)]
final class Endpoint {}

#[Endpoint]
function home(): string {
  return '/';
}

Targets

Pass one or more target flags to #[Attribute]:

FlagTarget
TARGET_CLASSclass, interface, or enum
TARGET_FUNCTIONnamed function or closure
TARGET_METHODmethod
TARGET_PROPERTYproperty
TARGET_CLASS_CONSTANTclass-like constant or enum case
TARGET_PARAMETERfunction, method, or closure parameter
TARGET_TYPE_ALIAStype alias
TARGET_NEWTYPEnewtype
TARGET_CONSTANTnamespace constant
TARGET_FILEsource file
TARGET_SYMBOLany named symbol
TARGET_ALLevery supported target

Join flags with |:

use Whim\Attribute\Attribute;

#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)]
final class Timed {}

With no flag, an attribute accepts every target.

Repeated attributes

An attribute may appear once on one target unless its flags include IS_REPEATABLE.

use Whim\Attribute\Attribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final readonly class Tag {
  public function __construct(public string $name) {}
}

#[Tag('http')]
#[Tag('public')]
final class Handler {}

Each use stores its own arguments. Source order stays intact.

File attributes

Use #![...] to attach attributes to the whole file. The opening #![ is one token. A file attribute list is a top-level statement and needs no semicolon.

namespace App;

use Whim\Attribute\Attribute;
use Whim\Reflection;

#[Attribute(Attribute::TARGET_FILE | Attribute::IS_REPEATABLE)]
final readonly class Tag {
  public function __construct(public string $name) {}
}

#![Tag(name: 'http')]

function home(): string {
  return '/';
}

$file = Reflection\reflect_function('App\\home')->getFile();
foreach ($file->getAttributes::<Tag>() as $attribute) {
  write_line!($attribute->newInstance()->name);
}

Put file attributes at the top level of a file or namespace, including after imports or executable statements. They apply to the whole file. The compiler collects them before execution and optimization. Function bodies and control-flow blocks contain only executable statements, so they cannot contain file attributes.

Names follow the namespace and imports at the attribute’s location. Arguments follow the same constant-expression rules as other attributes and cannot read local variables. All file attribute lists share one target, including lists in different namespaces. Repeating an attribute anywhere in the file requires IS_REPEATABLE. Reflection returns them in source order.

File attributes belong to the file, so a symbol’s getAttributes() does not include them. Use the symbol’s getFile() to read them. A file that contains only declarations, imports, and file attributes has no top-level code.

Reading attributes

Use Whim\Reflection to inspect attributes:

use Whim\Attribute\Attribute;
use Whim\Reflection;

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final readonly class Tag {
  public function __construct(public string $name) {}
}

#[Tag('http')]
#[Tag('public')]
final class Handler {}

$handler = Reflection\reflect_class('Handler');
assert!($handler != null);

foreach ($handler->getAttributes::<Tag>() as $attribute) {
  $tag = $attribute->newInstance();
  write_line!($tag->name);
}

Every declaration reflection provides getAttributes::<T>() and getAttributesByName(). Both return attribute reflections without running an attribute constructor. AttributeReflection::newInstance() constructs a fresh attribute value.

Use reflect_function() for a function. A class-like reflection provides its methods, properties, and constants. A method reflection provides its parameters. reflect_object($case)->getEnumCase() finds an enum case. Missing declarations and members return null.

See Reflection for the complete API.

Whim rejects non-constant arguments, missing attribute classes, invalid targets, and illegal repeats before top-level code runs. newInstance() checks argument values against the constructor’s parameter types.