Attributes

Since PHP 8.0, attributes attach structured, machine-readable metadata directly to a class, method, property, parameter, or function using #[...] syntax. Unlike the doc-comment annotations frameworks used to parse by hand, attributes are part of the language itself and can be read back at runtime through Reflection.
A class marked #[\Attribute] becomes usable as an attribute on other declarations. getAttributes() retrieves the ones applied to a given class, method, or property, and newInstance() builds the real object from the arguments written inside #[...].
<?php
declare(strict_types=1);

#[\Attribute]
class Route {
  public function __construct(public string $path, public string $method = "GET") {}
}

#[Route("/users", method: "POST")]
class UserController {
  #[Route("/users/{id}")]
  public function show(int $id) {}

  #[Route("/users/{id}/name")]
  public string $displayName = "";
}

$classReflection = new ReflectionClass(UserController::class);
foreach ($classReflection->getAttributes(Route::class) as $attribute) {
  $route = $attribute->newInstance();
  echo "Class route: {$route->path} [{$route->method}]\n";
}

$methodReflection = $classReflection->getMethod('show');
foreach ($methodReflection->getAttributes(Route::class) as $attribute) {
  $route = $attribute->newInstance();
  echo "Method route: {$route->path} [{$route->method}]\n";
}

$propertyReflection = $classReflection->getProperty('displayName');
foreach ($propertyReflection->getAttributes(Route::class) as $attribute) {
  $route = $attribute->newInstance();
  echo "Property route: {$route->path} [{$route->method}]\n";
}
?>

Class route: /users [POST] Method route: /users/{id} [GET] Property route: /users/{id}/name [GET]
Beyond custom attributes, PHP itself now recognizes several attributes natively – the engine acts on them directly, with no Reflection code of your own required. Here is a brief look at four of them. PHP 8.4's #[\Deprecated] marks a function or method as deprecated. Every call to it emits a real E_DEPRECATED notice automatically, including an optional custom message and version, without needing an explicit trigger_error() call:

<?php
class Calculator {
  #[\Deprecated(message: "use add() instead", since: "8.4")]
  public function sum($a, $b) {
    return $a + $b;
  }

  public function add($a, $b) {
    return $a + $b;
  }
}

$calc = new Calculator();
echo $calc->sum(2, 3) . "\n";
echo $calc->add(2, 3) . "\n";
?>

Deprecated: Method Calculator::sum() is deprecated since 8.4, use add() instead in D:\xampp\htdocs\attributes-deprecated.php on line 14 5 5
PHP 8.3's #[\Override] asserts that a method is meant to override one from a parent class, or implement one from an interface. Misspell the method name, or have the parent method renamed later, and PHP raises a fatal “no matching parent method exists” error at load time – instead of silently creating an unrelated new method:

<?php
class Base {
  public function greet(): string {
    return "Hi";
  }
}

class Child extends Base {
  #[\Override]
  public function greet(): string {
    return "Hello";
  }
}

$c = new Child();
echo $c->greet() . "\n";
?>

Hello
PHP 8.2's #[\SensitiveParameter] marks a parameter – typically a password or token – whose value must never leak into a stack trace. An uncaught exception's trace still lists every argument except that one, which is replaced with a SensitiveParameterValue placeholder object:

<?php
function login(string $username, #[\SensitiveParameter] string $password) {
  throw new \RuntimeException("Login failed");
}

try {
  login("alice", "secret123");
} catch (\RuntimeException $e) {
  echo $e->getTraceAsString() . "\n";
}
?>

#0 D:\xampp\htdocs\attributes-sensitive-parameter.php(7): login('alice', Object(SensitiveParameterValue)) #1 {main}
PHP 8.5's #[\NoDiscard] marks a function whose return value matters enough that silently ignoring it is probably a mistake – typically a pure function computing something with no side effects. Calling it without using the result triggers a warning, unless the call is explicitly cast to (void) to show the discard was intentional:

<?php
#[\NoDiscard]
function computeChecksum(string $data): string {
  return md5($data);
}

computeChecksum("hello");

$sum = computeChecksum("hello");
echo $sum . "\n";
?>

Warning: The return value of function computeChecksum() should either be used or intentionally ignored by casting it as (void) in D:\xampp\htdocs\attributes-nodiscard.php on line 7 5d41402abc4b2a76b9719d911017c592