PHP attributes attach namespaced, target-restricted metadata to declarations. Reflection discovers attribute names and arguments first, while newInstance resolves the class and runs constructor validation only when the consumer requests an object.
A dependable metadata system bounds discovery, defines repetition and inheritance policy, keeps constructors deterministic, centralizes interpretation, fails closed for security behavior, and tests every supported PHP runtime.
Mark a class with the built-in Attribute attribute and specify valid targets. Constructor arguments become the metadata payload when reflection instantiates it.
<?php
#[Attribute(Attribute::TARGET_METHOD)]
final class Route
{
public function __construct(
public string $method,
public string $path,
) {}
}
The target restriction catches accidental use on unsupported declarations.
Attribute arguments must be valid constant expressions for the supported PHP version. Keep the metadata small and descriptive; business logic belongs in ordinary code.
<?php
final class CourseController
{
#[Route('GET', '/courses')]
public function index(): array
{
return ['PHP', 'SQL'];
}
}
Filter getAttributes() by class name, then call newInstance() only when the application needs the attribute object. Instantiation runs its constructor and may throw if metadata is invalid.
<?php
$method = new ReflectionMethod(CourseController::class, 'index');
$attributes = $method->getAttributes(Route::class);
$route = $attributes[0]->newInstance();
echo $route->method . ' ' . $route->path;
GET /courses
The example depends on the Route and CourseController declarations directly above it.
Cache reflection discovery in long-running or frequently executed paths when profiling shows it matters. Do not hide critical authorization behavior behind metadata without tests and an obvious processing boundary.
| Need | Prefer |
|---|---|
| Require methods or behavior | Interface |
| Attach declaration metadata | Attribute |
| Change values by environment | Configuration |
| Perform one direct operation | Method call |
PHP attributes are structured metadata attached to declarations with `#[...]` syntax. An attribute name resolves to a class, and its arguments describe data that can later be inspected. Declaring metadata does not register a route, authorize a request, or execute a handler by itself.
Mark an attribute class with the built-in `Attribute` attribute. Its flag bitmask can allow classes, functions, methods, properties, class constants, parameters, or all supported targets. Narrow targets turn accidental placement into a detectable error when an instance is requested.
Make metadata classes small, immutable where practical, and explicit about invariants. Constructor promotion can expose values clearly, while constructor validation rejects impossible combinations when reflection creates the instance.
Resolve attribute classes through normal namespace rules. Import the class or use a qualified name, and avoid ambiguous short names in large metadata vocabularies. Renaming an attribute class is an API migration when source code or caches refer to it.
Attribute arguments use expressions permitted for attribute metadata in the supported PHP version, and named arguments follow the attribute constructor parameter names. Public constructor parameter names therefore become part of the source-facing metadata contract.
Prefer scalars, arrays, enum cases, and other stable descriptive values over service objects or environment-specific data. Runtime dependencies do not belong in metadata; the consumer should receive those dependencies normally and interpret the declaration.
Use named arguments when several values of the same type would be unclear, but treat renaming a parameter as a breaking change for attribute users. Validate optional combinations so omission has one documented meaning.
Keep secrets and tenant-specific configuration outside source attributes. Compiled or cached source metadata may be deployed broadly. Store changing operational values in protected configuration and let the attribute contain only a key or policy name when needed.
A target flag documents where an attribute belongs. ReflectionAttribute can report the actual target, but the consumer should usually reflect the declaration type it expects rather than discovering arbitrary metadata across the whole program.
An attribute is single-use on one declaration unless its class enables repeatability. Repeatable metadata fits independent route variants, tags, or handlers where ordering and duplicate semantics are defined. It is a poor substitute for one structured configuration object when entries must be coordinated.
`ReflectionAttribute::isRepeated()` reports whether the same attribute name was repeated on that declaration. The consumer still needs a policy for conflicts, order, duplicate identifiers, and whether one invalid entry rejects the entire declaration.
Attributes on a parent class or method are not an automatic behavioral inheritance mechanism. Decide explicitly whether a scanner examines parents, overridden methods, implemented interfaces, or traits, then test that resolution policy. Do not let reflection traversal accidentally define security behavior.
Reflection classes expose `getAttributes()` on classes, methods, functions, parameters, properties, and class constants. Calling it without a name returns every declared attribute; passing a class name filters exact matches and keeps unrelated framework metadata out of the consumer.
Use `ReflectionAttribute::IS_INSTANCEOF` with a base attribute class or interface when implementations form an intentional metadata family. Use the constant rather than its numeric value because constant values are implementation details that may change.
A ReflectionAttribute representation can reveal its name and arguments without constructing the attribute object. This separation supports discovery indexes, diagnostics, and controlled error handling before constructor code runs.
Scan a bounded, known set of application classes instead of reflecting every declared symbol on every request. Native autoloading and an explicit source map make discovery predictable. Third-party and generated classes should enter the scan only through deliberate registration.
`newInstance()` resolves the attribute class and invokes its constructor with the declared arguments. Missing classes, invalid targets, unknown named parameters, type mismatches, and constructor validation can therefore fail at this point rather than when the source file is parsed.
Catch failures at the discovery or application boot boundary that can report the exact class, member, and attribute. Do not ignore invalid metadata and continue with a partially registered security, routing, validation, or serialization configuration.
Keep attribute constructors deterministic and free from network, database, container, or global-state access. Construction should validate metadata, while the consumer performs behavior through explicitly injected collaborators.
Instantiate once per registration cycle when the object is immutable. Repeated creation on a hot request path adds work and can make failures appear late. Cache the validated interpretation rather than serializing Reflection objects with unclear lifecycle.
Use an attribute when information naturally belongs beside a declaration and tools need a structured way to inspect it. Use an interface when an object must provide behavior, ordinary method calls for direct control flow, and configuration when values vary by deployment or operator choice.
Avoid hidden chains in which one attribute triggers many distant side effects. Centralize interpretation in a named registry, compiler, or bootstrap step whose inputs and outputs can be inspected. This makes metadata-driven behavior debuggable without reading framework internals.
Authorization metadata can state a required policy, but the enforcement boundary must be unavoidable and tested. A forgotten scanner, unsupported target, or override policy must fail closed. Resource-specific authorization still needs runtime context.
Attributes couple source declarations to the metadata vocabulary. Keep that vocabulary cohesive and version it carefully. When removing an attribute, migrate consumers and caches before deleting the class so deployments with mixed code versions remain understandable.
Unit-test the attribute constructor with valid, boundary, and invalid arguments. Integration-test reflection on real declarations, including exact filtering, subtype filtering, repeated metadata, wrong targets, missing required values, and named arguments.
Test the consumer separately from discovery by passing validated metadata objects or descriptors. Then keep a smaller end-to-end test proving that an annotated declaration reaches the registry and changes the intended behavior.
Measure startup and request cost before adding metadata caches. In short-lived PHP requests, opcode caches may already reduce source loading while application discovery still costs reflection and allocation. In long-running workers, define when cached registration is rebuilt after deployment.
Run compatibility tests on the minimum and current supported PHP versions because permitted expressions, reflection details, and built-in attributes evolve. Label version-specific syntax in the lesson or codebase rather than allowing production parsing to become the first compatibility check.
0 of 2 checked
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.