Modernizing Laravel with PHP Attributes
Overview
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
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 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
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, 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
Tips & Gotchas
- Performance: While reflection has a tiny overhead, Laraveloften 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.
