Skip to content

PHP Tutorial Modern PHP Features

Attributes

Lesson 229 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

Attributes (PHP 8.0) attach metadata to classes, methods, properties, parameters and constants. They are written between #[ and ]. Frameworks read them to learn how to treat your code.

index.php
PHP
<?php
#[Attribute]
class Route
{
    public function __construct(public string $path, public string $method = "GET") {}
}

class BlogController
{
    #[Route("/blog", method: "GET")]
    public function list(): string
    {
        return "all posts";
    }

    #[Route("/blog/new", method: "POST")]
    public function create(): string
    {
        return "created";
    }
}

$class = new ReflectionClass(BlogController::class);

foreach ($class->getMethods() as $method) {
    foreach ($method->getAttributes(Route::class) as $attribute) {
        $route = $attribute->newInstance();
        echo $route->method . " " . $route->path . " -> " . $method->getName() . "\n";
    }
}
Output
OUTPUT
GET /blog -> list
POST /blog/new -> create

The attribute does nothing by itself. It is just data. The code at the bottom uses Reflection to find the attributes and decide what to do. A framework does this once to build its list of routes.

The syntax

index.php
PHP
#[Route("/blog")]                    // one attribute with an argument
#[Route("/blog"), Cache(60)]         // several, separated by commas
#[Attribute(Attribute::TARGET_METHOD)]   // limits where an attribute may be used

An attribute is a class. Its arguments go to the class constructor. Named arguments work too.

Limit where it can be used

index.php
PHP
<?php
#[Attribute(Attribute::TARGET_PROPERTY)]
class Required {}

class Form
{
    #[Required]
    public string $name = "";

    public string $note = "";
}

$props = (new ReflectionClass(Form::class))->getProperties();

foreach ($props as $prop) {
    $required = count($prop->getAttributes(Required::class)) > 0;
    echo $prop->getName() . ": " . ($required ? "required" : "optional") . "\n";
}
Output
OUTPUT
name: required
note: optional

Built-in attributes

PHP includes a few:

AttributeVersionPurpose
#[\ReturnTypeWillChange]8.1Silences a return type deprecation in older code
#[\AllowDynamicProperties]8.2Allows dynamic properties on a class
#[\SensitiveParameter]8.2Hides a parameter’s value in stack traces
#[\Override]8.3Checks that a method really overrides a parent method
#[\Deprecated]8.4Marks your own code as deprecated
#[\NoDiscard]8.5Warns when a return value is ignored
index.php
PHP
<?php
function login(string $user, #[\SensitiveParameter] string $password): void
{
    // if an exception happens here, the password is not printed in the stack trace
}

Attributes vs comments

Before PHP 8, frameworks put metadata in docblock comments (@Route("/blog")) and parsed the text. Attributes are real syntax, so PHP checks them, and tools can read them reliably.

Where you will see them

  • Symfony routes and validation rules.
  • Laravel has a growing number of attributes.
  • Doctrine ORM mappings (#[ORM\Entity]).
  • Testing tools (PHPUnit’s #[Test]).

Older PHP and the # symbol

In older PHP, # starts a comment. #[Route()] is therefore treated as a comment on PHP 7, which is why attributes are backward compatible for simple cases.

info
Why this matters in Laravel and Symfony
Modern framework code uses attributes for routes, validation and configuration. When you see #[Route("/x")] above a method, you now know a class is being read through Reflection.