# PHP Attributes and Metaprogramming

# Introduction

Attributes were introduced in PHP 8 around four years ago. As outlined in the [**RFC**](https://wiki.php.net/rfc/attributes_v2), attributes provide a structured and syntactic way to add metadata to declarations such as classes, properties, functions, methods, parameters, and constants. Previously, developers relied on **DocBlocks'** annotations  
for [this purpose](https://www.doctrine-project.org/projects/doctrine-annotations/en/stable/index.html). The syntax of Attributes is as follows:

```php
 <?php

#[Attribute]
class ExampleAttribute
{
    public function __construct(public string $message) {}
}

#[ExampleAttribute('This is a custom attribute for a class')]
class MyClass
{
    #[ExampleAttribute('This is a custom attribute for a method')]
    public function myMethod() { echo "Hello from myMethod!"; }
}
```

But first, how are PHP attributes different from decorators in Python? Or, Java’s Aspect-oriented Programming (AOP) annotations?

# Metaprogramming

The concept that is fundamentally common across Aspect-Oriented Programming (AOP), attributes, and decorators is metaprogramming. Metaprogramming is the practice of writing code that generates, manipulates, or modifies other code. Essentially, it's code that writes code. This can be done at compile-time or runtime, depending on the language and use case.

## Decorator

Decorator is a behavioral design pattern where a function or class is wrapped with additional behavior without modifying its structure. Achieved using higher-order functions (in Python, JavaScript) or annotations (in TypeScript).

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This is different from the <a target="_self" rel="noopener noreferrer nofollow" href="https://refactoring.guru/design-patterns/decorator" style="pointer-events: none">stackable decorator classes</a> implementation of the decorator pattern.</div>
</div>

The following is a simple example of a logging decorator in Python.

```python
def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} returned {result}")
        return result
    return wrapper

@log_function_call
def add(a, b):
    return a + b

# Usage
add(3, 4)
```

* `log_function_call` is a **decorator function**.
    
* `@log_function_call` applies that decorator to the `add` function.
    
* `wrapper` is a nested function that wraps the original function, adding extra behavior before and after it runs.
    

The output of this code will be:

```bash
Calling function: add
Function add returned 7
```

## Aspect-Oriented Programming

Aspect-Oriented Programming (AOP) is a paradigm that allows you to modularize concerns (like logging, security, or transaction management) that cut across the typical divisions of responsibility (such as methods or classes) by encapsulating them into reusable modules called aspects, improving separation of concerns and making the codebase easier to maintain and extend.

Let’s take another logging example but this time implemented in AOP to explain its core concept:

```java
package com.example.demo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.demo.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("[LOG] Method called: " + joinPoint.getSignature().toShortString());
    }
}
```

The code defines a **cross-cutting concern** — specifically, **logging** — and **separates** it from the main business logic of the application (like the `service` classes).  
Instead of adding `System.out.println` statements inside every service method, **you modularized** this behavior into a **separate aspect**: `LoggingAspect`.

| Element | Explanation |
| --- | --- |
| `@Aspect` | Declares this class as an **aspect** (a modular unit of a cross-cutting concern). |
| `@Component` | Tells Spring to manage this class as a **bean**. |
| `@Before(...)` | A **pointcut** and **advice**: it says "Before any method in `com.example.demo.service.*` package is called, run `logBefore()`." |
| `JoinPoint` | Gives details about the method being intercepted (like its name). |
| `logBefore()` | This is the **advice method** where the logging action happens. |

In essence, metaprogramming is the conceptual thread that ties AOP, attributes, and decorators together—they’re all ways to step outside the normal flow of code execution and define how code should behave beyond its immediate implementation.

# Reflection API

Both the new first-class citizen attributes and the dockblocks implementation depend on the reflection API for inspecting the attributes at runtime. [Reflection AP](https://www.php.net/manual/en/intro.reflection.php) is another, much older, example of metaprogramming in PHP. It introduced the ability to introspect classes, interfaces, functions, methods and extensions. It can also retrieve the doc comments for functions, classes and methods.

If you are not familiar with it, here is a simple example where the properties of the `User` class can be accessed programmatically regardless of their access modifier:

```php
<?php

class User {
    public function __construct(
        public string $name,
        private int $age,
        protected string $email
    ) {}
}

// Use ReflectionClass to inspect the User class
$reflection = new ReflectionClass('User');

// Get the public, protected, and private properties 
$properties = $reflection->getProperties();
```

# Attributes

## Syntax

Attributes are classes annotated with the base `#[Attribute]`:

```php
#[Attribute(Attribute::TARGET_METHOD)]
class Log
{
    public function __construct(public string $message = 'Method called') {}
}
```

Attributes are configured by passing a [bitmask](https://en.wikipedia.org/wiki/Mask_\(computing\)#:~:text=In%20computer%20science%2C%20a%20mask,in%20a%20single%20bitwise%20operation.) as its first argument, It can be used to restrict the types that an attribute can be applied to. In the above example, the `Log` attribute can only be applied to methods. The available types are:

* `TARGET_CLASS`
    
* `TARGET_FUNCTION`
    
* `TARGET_METHOD`
    
* `TARGET_PROPERTY`
    
* `TARGET_CLASS_CONSTANT`
    
* `TARGET_PARAMETER`
    
* `TARGET_ALL`
    

By default, an attribute can be used once per declaration. to change this behavior, specify it in the bitmask of the `#[Attribute]` declaration using the `Attribute::IS_REPEATABLE` flag:

```php
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Log
{
    public function __construct(public string $message = 'Method called') {}
}
```

## Usage

1. Create a class that uses the `Log` attribute:
    
    ```php
    class AccountService
    {
        #[Log("Creating a new account")]
        public function createAccount(string $user)
        {
            echo "Account created for $user" . PHP_EOL;
        }
    
        #[Log("Deleting an account")]
        public function deleteAccount(string $user)
        {
            echo "Account deleted for $user" . PHP_EOL;
        }
    
        public function helperFunction()
        {
            echo "This won't be logged." . PHP_EOL;
        }
    }
    ```
    
2. Create a logger proxy function to call the methods with logging:
    
    ```php
    function callWithLogging(object $object, string $method, array $args = [])
    {
        $refMethod = new ReflectionMethod($object, $method);
        $attributes = $refMethod->getAttributes(Log::class);
    
        if (!empty($attributes)) {
            /** @var Log $log */
            $log = $attributes[0]->newInstance();
            echo "[LOG] {$log->message}" . PHP_EOL;
        }
    
        return $refMethod->invokeArgs($object, $args);
    }
    ```
    

## Client Code

```php
$service = new AccountService();

callWithLogging($service, 'createAccount', ['alice']);
callWithLogging($service, 'deleteAccount', ['bob']);
callWithLogging($service, 'helperFunction'); // No log, no attribute
```

The output of this code will be:

```bash
[LOG] Creating a new account
Account created for alice
[LOG] Deleting an account
Account deleted for bob
This won't be logged.
```

# Real-Life Example

Let’s take a real-life example of attributes. **Laravel 11** introduced [contextual attributes](https://laravel.com/docs/12.x/container#contextual-attributes) and for our example we will be using the [`Config`](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Container/Attributes/Config.php) attribute. The `#[Config(...)]` attribute allows you to annotate a constructor or method parameter, so that **Laravel’s** service container can **resolve and inject** the corresponding config value at runtime:

```php
<?php
 
namespace App\Http\Controllers;

use Illuminate\Container\Attributes\Config;
 
class PhotoController extends Controller
{
    public function __construct(#[Config('app.timezone')] protected string $timezone)
    {
        // ...
    }
}
```

## Config Attribute Definition

The `Config` attribute definition goes as follows:

```php
<?php

namespace Illuminate\Container\Attributes;

use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;

#[Attribute(Attribute::TARGET_PARAMETER)]
class Config implements ContextualAttribute
{
    /**
     * Create a new class instance.
     */
    public function __construct(public string $key, public mixed $default = null) {}

    /**
     * Resolve the configuration value.
     *
     * @param  self  $attribute
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @return mixed
     */
    public static function resolve(self $attribute, Container $container)
    {
        return $container->make('config')->get($attribute->key, $attribute->default);
    }
}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">The <code>ContextualAttribute</code> is an empty interface in Laravel 12.x.</div>
</div>

## Resolving `Config` Attribute

Contextual attributes resolving start in `Illuminate\Container\Container::build($concrete)`.

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Some of the code in <code>Illuminate\Container\Container</code> will be omitted if it’s not related to the flow of binding a constructor’s config parameter. The full class can be found <a target="_self" rel="noopener noreferrer nofollow" href="https://github.com/laravel/framework/blob/12.x/src/Illuminate/Container/Container.php" style="pointer-events: none">here</a></div>
</div>

First, we create a `ReflectionClass` to inspect the `$concrete` class:

```php
try {
    $reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
    throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
}
```

The next step is to get the `$concrete` constructor method:

```php
// returns a ReflectionMethod instance
$constructor = $reflector->getConstructor();
```

Then, if the class does not have a constructor, the service container returns a new instance of `$concrete`. But, that is not the case in our `PhotoController`, so that, it gets a list of the `$constructor` method parameters:

```php
// returns an array of ReflectionParameter[]
$dependencies = $constructor->getParameters();

try {
    $instances = $this->resolveDependencies($dependencies);
} catch (BindingResolutionException $e) {
    array_pop($this->buildStack);

    throw $e;
}
```

The method `Container::resolveDependencies($dependencies)` loops over the `$dependencies` array, and returns an array of the constructor parameters. One execution flow is when the `$dependency` has a contextual parameter:

```php
foreach ($dependencies as $dependency) {
    $result = null;

    if (! is_null($attribute = Util::getContextualAttributeFromDependency($dependency))) {
        $result = $this->resolveFromAttribute($attribute);
    }
}
```

Finally, in `Container::resolveFromAttribute(ReflectionAttribute $attribute)`, There are two scenarios for resolving the value of the attribute:

```php
public function resolveFromAttribute(ReflectionAttribute $attribute)
{
    $handler = $this->contextualAttributes[$attribute->getName()] ?? null;

    $instance = $attribute->newInstance();

    if (is_null($handler) && method_exists($instance, 'resolve')) {
        $handler = $instance->resolve(...);
    }

    if (is_null($handler)) {
        throw new BindingResolutionException("Contextual binding attribute [{$attribute->getName()}] has no registered handler.");
    }

    return $handler($instance, $this);
}
```

1. If there is a custom handler function (or callable) for the `$attribute` name defined in the `App\Providers\AppServiceProvider::boot()`:
    
    ```php
    use Illuminate\Container\Attributes\Config;
    
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->app->contextualAttributes[Config::class] = fn() => 'Africa/Cairo';
    }
    ```
    
    The callable `fn() => 'Africa/Cairo'` is invoked.
    
2. If the `$handler` is `null`, and the `Illuminate\Container\Attributes\Config::resolve()` method exists in the attribute definition, it will be invoked.
    

# Attributes Misuse

Metaprogramming allows code to generate or modify other code dynamically, offering flexibility and reduced boilerplate, but it comes with significant downsides. It can reduce readability and maintainability, make debugging harder, and introduce performance overhead.

To minimize the negative side effects of attributes, they should not be used beyond their described purpose as metadata providers. Misuse examples includes:

* **Embedding logic** or **side effects** within attributes
    
    * For example, having an attribute that *modifies global state* or *performs operations* at runtime just by being present.
        
* **Replacing business logic** with attribute logic
    
    * E.g., putting application logic or decisions into attribute classes, rather than keeping that logic in controllers or services.
        
* **Overloading attribute meaning**
    
    * When attributes are overloaded with too many responsibilities or become a place to "hide" logic, it becomes harder to understand or maintain code.
        
* **Using attributes for code that should remain in configuration files**
    
    * E.g., trying to implement environment configuration through attributes.
        

In conclusion, PHP attributes provide a powerful and expressive way to add metadata to your code, making it easier to build more declarative and maintainable applications. From [core language features](https://www.php.net/manual/en/reserved.attributes.php) like `#[Deprecated]`, `#[Override]`, and `#[Attribute]`, to [PHPUnit-specific](https://docs.phpunit.de/en/10.5/attributes.html) attributes such as `#[Test]`, `#[Before]`, `#[After]`, and `#[Depends]`, attributes help streamline testing and application behavior without relying on verbose annotations or docblocks. As attributes continue to evolve in PHP, they open up cleaner integration points for frameworks and tooling. Whether you're writing application logic or unit tests, leveraging built-in and custom attributes can greatly improve the readability and structure of your codebase.
