PHP Attributes

Ma Mariya Vinciya Updated 21 Oct 2026
5 min read ·Lesson 167 of 174
In this post

What Are Attributes? In simple terms: Attributes are labels you attach to classes, methods, properties, parameters, functions, or constants to add extra information about them. The label itself does n

What Are Attributes?

In simple terms: Attributes are labels you attach to classes, methods, properties, parameters, functions, or constants to add extra information about them. The label itself does nothing; other code — PHP itself, a framework, or your own code — can read it and act on it. For example, #[Route('/products')] tells a framework which URL a controller method handles, and #[Email] on a property tells a validator what to check.

Technically: Attributes (PHP 8.0+) are structured, machine-readable metadata. They are written as #[AttributeName(arguments)] directly before a declaration. Each attribute name refers to a class marked with #[Attribute]. At runtime, the Reflection API returns the attributes of a declaration, and newInstance() creates an object of the attribute class with the given arguments.

Why Attributes Exist

Before PHP 8, frameworks put such metadata in docblock comments (/** @Route("/products") */), which had to be parsed as text, were not validated by PHP, and could contain typos that went unnoticed. Attributes are part of the language syntax: PHP parses them, they refer to real classes, and IDEs understand them.


Syntax

#[AttributeName]
#[AttributeName(argument1, named: value)]
#[First, Second(10)]           // several attributes in one block

 

Attributes can be placed before:

TargetExample
Classes, interfaces, traits, enums#[Entity] final class Product
Methods and functions#[Route('/products')] public function index()
Properties and class constants#[Column(length: 100)] public string $name;
Parameters (including promoted ones)public function __construct(#[Email] public string $email)

 


Built-In Attributes

PHP itself understands several attributes:

AttributeSinceEffect
#[\Override]8.3Error if the method does not override a parent or interface method (see Method Overriding)
#[\SensitiveParameter]8.2Hides the parameter's value in stack traces (passwords, API keys)
#[\Deprecated]8.4Calling the function, method, or class constant emits a deprecation notice
#[\AllowDynamicProperties]8.2Allows dynamic properties on a class (see PHP Properties)
#[\ReturnTypeWillChange]8.1Silences return-type deprecation notices when extending internal classes
#[\NoDiscard]8.5Warns when the return value of a function is not used

 

Example: Protecting Secrets and Marking Old APIs

<?php
function connectToPaymentGateway(string $merchantId, #[\SensitiveParameter] string $apiKey): void
{
    throw new RuntimeException('Gateway unreachable');
}

final class PriceCalculator
{
    #[\Deprecated(message: 'use priceWithTax() instead', since: '2.0')]
    public function grossPrice(float $net): float
    {
        return $this->priceWithTax($net);
    }

    public function priceWithTax(float $net): float
    {
        return round($net * 1.18, 2);
    }
}

 

  • If connectToPaymentGateway() throws, stack traces and error logs show the API key as a placeholder object (SensitiveParameterValue) instead of the real key — an important protection, because stack traces often end up in log files and monitoring tools.
  • Calling grossPrice() triggers an E_USER_DEPRECATED notice containing the message and version, which helps teams migrate away from old methods.

Creating Your Own Attributes

An attribute is a normal class marked with the built-in #[Attribute] attribute. Its constructor parameters define the arguments it accepts:

#[Attribute(Attribute::TARGET_PROPERTY)]
final class Length
{
    public function __construct(public int $min, public int $max) {}
}

 

FlagAllows the Attribute On
Attribute::TARGET_CLASSClasses
Attribute::TARGET_METHODMethods
Attribute::TARGET_PROPERTYProperties
Attribute::TARGET_PARAMETERParameters
Attribute::TARGET_FUNCTION, TARGET_CLASS_CONSTANTFunctions, class constants
Attribute::TARGET_ALLEverything (default)
Attribute::IS_REPEATABLEUsing the same attribute several times on one declaration

 

Flags are combined with |, for example Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE.


Practical Example: A Validator Driven by Attributes

<?php
interface Rule
{
    public function check(mixed $value): ?string;
}

#[Attribute(Attribute::TARGET_PROPERTY)]
final class Length implements Rule
{
    public function __construct(public int $min, public int $max) {}

    public function check(mixed $value): ?string
    {
        $length = mb_strlen((string) $value);

        return $length < $this->min || $length > $this->max
            ? "must be {$this->min}–{$this->max} characters"
            : null;
    }
}

#[Attribute(Attribute::TARGET_PROPERTY)]
final class Email implements Rule
{
    public function check(mixed $value): ?string
    {
        return filter_var($value, FILTER_VALIDATE_EMAIL) === false ? 'must be a valid email address' : null;
    }
}

final class SignupRequest
{
    public function __construct(
        #[Length(min: 2, max: 30)]
        public string $name,
        #[Email]
        public string $email,
        #[Length(min: 8, max: 64)]
        public string $password,
    ) {}
}

function validate(object $object): array
{
    $errors = [];

    foreach ((new ReflectionObject($object))->getProperties() as $property) {
        foreach ($property->getAttributes(Rule::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
            $message = $attribute->newInstance()->check($property->getValue($object));

            if ($message !== null) {
                $errors[$property->getName()][] = $message;
            }
        }
    }

    return $errors;
}

print_r(validate(new SignupRequest('A', 'asha@', 'secret123')));
var_dump(validate(new SignupRequest('Asha', 'asha@example.com', 'correct-horse-42')) === []);

 

Output

Array
(
    [name] => Array
        (
            [0] => must be 2–30 characters
        )

    [email] => Array
        (
            [0] => must be a valid email address
        )

)
bool(true)

 

Step-by-Step Explanation

  1. Length and Email are attribute classes. They also implement a Rule interface, so the validator can treat all rules the same way.
  2. SignupRequest declares its rules next to the properties they apply to. Attributes on promoted constructor parameters apply to the generated properties as well.
  3. validate() uses Reflection: getAttributes(Rule::class, ReflectionAttribute::IS_INSTANCEOF) returns all attributes whose class implements Rule.
  4. newInstance() creates the attribute object with its arguments (min: 2, max: 30), which then checks the property's value.
  5. Adding a new rule (for example, #[Matches('/^[A-Z]/')]) only requires a new attribute class — the validator does not change.

This is exactly how Symfony's Validator (#[Assert\Email]), Doctrine's ORM mapping (#[ORM\Column]), and routing attributes (#[Route('/products')]) work.


When Are Attributes Evaluated?

  • PHP parses attributes when the file is compiled, but attribute classes are not instantiated until code calls newInstance().
  • The attribute class does not even need to exist until then. Errors such as a missing class or a wrong target are reported only when newInstance() is called.
  • Reflection is relatively expensive; frameworks typically read attributes once and cache the results.

Common Mistakes

Expecting Attributes to Do Something on Their Own

Effect: Adding #[Email] without a validator that reads it has no effect.

Fix: Attributes are metadata; some code must read and act on them.

Forgetting #[Attribute] on the Attribute Class

Effect: newInstance() throws an error ("Attempting to use non-attribute class ... as attribute").

Fix: Mark every attribute class with #[Attribute].

Wrong Target

Effect: newInstance() throws when an attribute is used on a declaration its flags do not allow.

Fix: Set the correct TARGET_* flags.

Putting Logic in Attributes That Belongs in Code

Effect: Behavior hidden in metadata is hard to follow and test.

Fix: Use attributes for declarative configuration; keep business logic in services.


Best Practices

  • Use built-in attributes: #[\Override], #[\SensitiveParameter], and #[\Deprecated].
  • Use attributes for declarative configuration: validation rules, routes, ORM mapping, serialization.
  • Restrict attribute targets and keep attribute classes small and immutable.
  • Read attributes through Reflection in one place and cache the results.
  • Prefer attributes over docblock annotations in modern code.

Official reference: https\://www.php.net/manual/en/language.attributes.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer
Ask about this post
AI Ask about this post

Ask questions about PHP Attributes and get answers drawn from it.

Signed-in readers only.