Modernizing Laravel with PHP Attributes
Overview
offer a native, machine-readable way to add metadata to classes, methods, and properties. In the context of , 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 models, the , and basic dependency injection patterns.
Key Libraries & Tools
- : The engine providing the attribute syntax and reflection capabilities.
- : The framework implementing these specific attribute-driven features.
- : 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 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, 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.
- 47%· products
- 27%· products
- 13%· products
- 7%· products
- 7%· products

We Love PHP Attributes
WatchLaravel // 10:02
The official YouTube channel of Laravel, the clean stack for Artisans and agents. We will update you on what's new in the world of Laravel, from the framework to our products Cloud, Forge, and Nightwatch.