Step 1 of 4 · Reading · ~2 min
Learn
Reflection and Attributes
PHP 8 added native attributes — metadata you can attach to classes, methods, properties, parameters, then read via reflection.
Before you start: this course's grader runs PHP 7.4.1, where #[...] is a
parse error. Read this lesson for the syntax you will use in a modern codebase; the
exercise asks for the pre-8.0 doc-block form described at the bottom, which is what
the grader can run.
<?php
#[Attribute(Attribute::TARGET_METHOD)]
class Route {
public function __construct(
public readonly string $path,
public readonly string $method = 'GET',
) {}
}
class UserController {
#[Route(path: '/users', method: 'GET')]
public function list() { ... }
#[Route(path: '/users/{id}', method: 'GET')]
public function show(int $id) { ... }
#[Route(path: '/users', method: 'POST')]
public function create() { ... }
}
Reading attributes:
$rc = new ReflectionClass(UserController::class);
foreach ($rc->getMethods() as $method) {
foreach ($method->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance(); // instantiates the Route
echo "{$route->method} {$route->path} -> {$method->getName()}\n";
}
}
Note: attributes are PHP classes annotated with #[Attribute]. They're inert until you explicitly read them via reflection.
Attribute::TARGET_* flags:
TARGET_CLASS,TARGET_METHOD,TARGET_PROPERTY,TARGET_PARAMETER,TARGET_FUNCTION,TARGET_CLASS_CONSTANT,TARGET_ALL- Combine with
|:Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION Attribute::IS_REPEATABLE— allow multiple of the same attribute
Use cases:
- Routing (Symfony, Laravel route attributes)
- Validation (
#[Required],#[Min(0)],#[Email]) - ORM mapping (
#[Column(type: 'string')],#[Index]) - Authorization (
#[RequiresRole('admin')]) - Test discovery (
#[Test]for PHPUnit-like frameworks)
Before PHP 8, frameworks carried the same metadata in doc-comments, parsed by libraries like Doctrine Annotations. Reflection can read a doc-block directly, so the whole mechanism is a regex over a string:
class Controller {
/** @Route("/users") */
public function list() {}
}
$m = new ReflectionMethod(Controller::class, 'list');
$doc = $m->getDocComment(); // the /** ... */ text, or false
preg_match('/@Route\("([^"]*)"\)/', $doc, $hit);
echo $hit[1]; // /users
Native attributes are a real upgrade over this — language-level support, IDE autocomplete, type-checked arguments, and no regex — but the doc-block version shows that "metadata plus reflection" was always the idea.
Modern PHP framework code (Symfony 6+, Laravel 9+) leans heavily on attributes for declarative APIs.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…