Modernizing Laravel with PHP Attributes

Overview

offer a native, machine-readable way to add metadata to classes, methods, and properties. In the context of
Laravel
, they replace traditional boilerplate methods like booted() or newCollection() with declarative tags. This shift moves configuration out of the logic flow and places it directly where it belongs: at the top of the class definition. It improves readability and makes the intent of a model or controller immediately clear to any developer opening the file.

Prerequisites

To follow this guide, you should have a solid grasp of

or higher, as attributes were introduced in that version. You should also be comfortable with
Laravel Eloquent
models, the
Laravel Service Container
, and basic dependency injection patterns.

Key Libraries & Tools

  • PHP
    : The engine providing the attribute syntax and reflection capabilities.
  • Laravel
    : The framework implementing these specific attribute-driven features.
  • Laravel Eloquent
    : The database layer where attributes define scopes and observers.

Code Walkthrough

Declarative Global Scopes

Traditionally, you would apply a global scope inside a model's booted method. With attributes, you simply tag the class.

#[ScopedBy(ActiveScope::class)]
class User extends Model
{
    // No booted method required
}

This tells

to use
PHP
to check for the ScopedBy attribute and apply the logic automatically.

Model Observers and Collections

Cleaning up the model continues by moving observers and custom collection definitions to the class header.

#[ObservedBy(UserObserver::class)]
#[CollectedBy(UserCollection::class)]
class User extends Model
{
}

This replaces the observe() call and the newCollection() method override, keeping the model body focused purely on business logic.

Contextual Dependency Injection

Attributes shine in controllers or form requests where you need specific implementations injected. Instead of manual binding in a

, use contextual attributes.

public function authorize(
    #[CurrentUser] User $user,
    #[RouteParameter('user')] User $userToUpdate
): bool {
    return $user->id === $userToUpdate->id;
}

Here, #[CurrentUser] fetches the authenticated user, while #[RouteParameter] extracts a specific model from the

.

Syntax Notes

Attributes use the #[ClassName] syntax. They can accept positional or named arguments, much like a class constructor. Under the hood,

utilizes PHP's ReflectionClass to find these attributes at runtime and execute the associated framework logic.

Practical Examples

Use these attributes when building multi-tenant applications where a TenantScope must be applied to dozens of models. It is also highly effective in complex API controllers where you need to inject specific configurations, such as a

cache implementation vs. a database implementation, directly into the method signature.

Tips & Gotchas

  • Performance: While reflection has a tiny overhead,
    Laravel
    often caches these results, making the performance impact negligible.
  • Namespace Imports: Always remember to import the attribute class (e.g., use Illuminate\Database\Eloquent\Attributes\ScopedBy;) or your code will fail silently or throw an error.
  • Readability: Don't over-stack attributes. If a class needs ten different attributes, it might be a sign the class is handling too many responsibilities.
3 min read