Skip to content
Lesson 4 of 7

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.

Up nextEnums and Match ExpressionsModern PHP 8+

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…