attributes
constructor parameters become the attribute's named or positional arguments. - Restrict where an attribute may appear with target flags — Pass target flags to constrain placement, e.g. #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)], and add Attribute::IS_REPEATABLE only when the…
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "attributes"
description: "constructor parameters become the attribute's named or positional arguments. - Restrict where an attribute may appear with target flags — Pass target flags to constrain placement, e.g. #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)], and add Attribute::IS_REPEATABLE only when the…"
license: "MIT"
---
<!-- generated: do not edit — source: knowledge/atoms/php/attributes/ -->
<!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 -->
<!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->
# PHP Attributes
## When to use
- The user wants to attach structured, machine-readable metadata to a class, method, property, or parameter on PHP 8.0+.
- The user declares framework metadata such as routes, validation constraints, or ORM column mapping directly on the code that owns it.
- The user wants to migrate docblock annotations (Doctrine-style `@Route`, `@ORM`) to native PHP 8 attributes.
## When NOT to use
- Do not use attributes when the target runtime is PHP 7.x; keep docblock annotations parsed by a library instead.
- Do not use attributes when the metadata must change at runtime or come from configuration; use a config file or a service instead.
## Core guidance
- **Mark attribute classes with** — Declare an attribute as a plain class annotated with `#[Attribute]`. Its
constructor parameters become the attribute's named or positional arguments.
- **Restrict where an attribute may appear with target flags** — Pass target flags to constrain placement, e.g.
`#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]`, and add
`Attribute::IS_REPEATABLE` only when the same attribute may appear twice.
## Quick recipe
1. Create the attribute class marked `#[Attribute(Attribute::TARGET_METHOD)]` with a
promoted typed constructor holding the route `$path` and `$method`. (verify: The class can be placed above a controller method without an IDE warning)
2. Place `#[Route('/users/{id}', method: 'GET')]` directly above the handler method
so the route lives with the code it maps to. (verify: The attribute arguments match the constructor signature exactly)
3. At boot, reflect each controller, call
`$method->getAttributes(Route::class)` then `->newInstance()`, and register the
resulting routes into a cached table. (verify: A request to the declared path dispatches to the annotated method)
## Best practices
- Scan attributes once at build or boot time and cache the resolved map (routes,
constraints) rather than reflecting classes on every request
- Pass only compile-time constant expressions as attribute arguments — literals,
constants, `::class`, and enum cases (PHP 8.1+)
- Read attributes with `$ref->getAttributes(Route::class)` and call
`->newInstance()` on each result to get a typed attribute object, rather than
inspecting raw argument arrays
- Give the attribute class a promoted, typed constructor
(`public function __construct(public string $path)`) so its arguments are stored
as readonly-friendly typed properties
## Anti-patterns
| Anti-pattern | Why it fails | Do instead |
| --- | --- | --- |
| Parsing docblocks by hand on PHP 8 | Docblock parsing is stringly-typed, invisible to the engine, and breaks silently when comments are stripped. | Declare the metadata as a native attribute class and read it with<br>`getAttributes()`, which the engine parses and IDEs verify. |
| Putting behavior inside the attribute class | An attribute is passive metadata; logic there executes unpredictably at Reflection<br>time and couples declaration to behavior. | Keep the attribute a plain data holder; put behavior in the consumer that reads it<br>via Reflection. |
| Reflecting attributes on every request | Repeated Reflection scans add avoidable per-request cost that grows with the number of annotated classes. | Resolve attributes once and cache the compiled map (route table, constraint list),<br>invalidating it only when source files change. |
See [references/anti-patterns.md](references/anti-patterns.md).
## Common mistakes
1. **getAttributes() returns reflectors, not instances** — `getAttributes()` returns `ReflectionAttribute` objects, not your attribute
instances; reading `->getName()` or `->getArguments()` skips constructor logic. Fix: Call `->newInstance()` on each `ReflectionAttribute` to get the real,
constructor-validated attribute object before using it.
2. **Attributes are not inherited by getAttributes()** — `ReflectionClass::getAttributes()` reads only attributes declared on that class,
not attributes on parent classes or implemented interfaces. Fix: Walk the class hierarchy explicitly (`getParentClass()`, `getInterfaces()`) and
merge their attributes if inherited metadata is required.
3. **newInstance() errors when the class lacks** — Calling `newInstance()` on a class that is not itself annotated with
`#[Attribute]` throws `Error: Attempting to use non-attribute class ... as attribute`. Fix: Add `#[Attribute]` (with target flags) to every class you intend to use as an
attribute.
## Version notes
- **Attributes introduced in PHP 8.0** (PHP >=8.0) — The `#[Attribute]` syntax, `ReflectionAttribute`, and `getAttributes()` are available from PHP 8.0.
- **Enum cases usable as attribute arguments in PHP 8.1** (PHP >=8.1) — From PHP 8.1, enum cases are valid constant expressions, so they can be passed directly as attribute arguments.
## Security
- Not applicable — no I/O, auth, or untrusted input.
## Testing
- Activation and decision behavior are verified in `eval/scenarios/php/attributes.yaml`.
## Additional resources
- [references/anti-patterns.md](references/anti-patterns.md)
- [references/core.md](references/core.md)
- [references/examples.md](references/examples.md)
- [references/pitfalls.md](references/pitfalls.md)
- [references/recipes.md](references/recipes.md)
- [references/version-differences.md](references/version-differences.md)
## Knowledge graph
**Prerequisites:** [php/typed-properties](../../php/typed-properties/SKILL.md)
**Related:** [php/enums](../../php/enums/SKILL.md), [php/migrate-php74-to-php80](../../php/migrate-php74-to-php80/SKILL.md), [phpstan/custom-rules](../../phpstan/custom-rules/SKILL.md), [symfony/dependency-injection](../../symfony/dependency-injection/SKILL.md)
<!-- graph-fragment: graph/fragments/php/attributes.yaml -->More General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

