Define Observers and Global Scopes using PHP's Attributes in Laravel
PHP 8’s Attributes are a great way to add metadata to your code. They are a form of structured, syntactic metadata that can be added to classes, methods, functions, and properties.
Attributes are great when you want to attach data to your classes that can be seen “at a glance” while still being able to access them programmatically.
You can read more about PHP 8’s Attributes in this article where I explained their inner workings and how you can use them in your code.
There are a couple of new features in Laravel 10.x now make use of this feature.
These are:
- To define observers for Eloquent models.
- To define global scopes for Eloquent models.
In this article, we’ll see how we can define these using PHP’s Attributes in Laravel.
Defining Observers using Attributes
Observers in Laravel are classes that listen for specific events on Eloquent models. They are used to group all the event listeners for a model into a single class.
Traditionally, you would define a model observer like so.
namespace App\Observers;
class UserObserver
{
public function creating(User $user)
{
//
}
}
And then you would attach this observer to the model like so.
use App\Observers\UserObserver;
class User extends Model
{
public function boot(): void
{
User::observe(UserObserver::class);
}
}
But with PHP 8’s Attributes, you can define the observer like so.
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use App\Observers\UserObserver;
#[ObservedBy(UserObserver::class)]
class User extends Model
{
//
}
As you can tell, Laravel provides an ObservedBy
attribute that you can use to define the observer for the model. You can pass the observer class to the ObservedBy
attribute.
So, now you don’t need to define the boot
method in the model to attach the observer. You can do it directly using the ObservedBy
attribute.
Defining Global Scopes using Attributes
Global scopes allow you to add constraints to all queries for a given model.
Traditionally, you would define a global scope like so.
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
class AgeScope
{
public function apply(Builder $builder, $model)
{
$builder->where('age', '>', 30);
}
}
And then you would attach this global scope to the model like so.
use App\Scopes\AgeScope;
class User extends Model
{
protected static function booted()
{
static::addGlobalScope(new AgeScope);
}
}
PHP’s Attributes simplify this process.
With PHP 8’s Attributes, you can define the global scope like so.
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
use App\Scopes\AgeScope;
#[ScopedBy(AgeScope::class)]
class User extends Model
{
//
}
As you can tell, Laravel provides a ScopedBy
attribute that you can use to define the global scope for the model. You can pass the global scope class to the ScopedBy
attribute.
You can even pass multiple global scopes to the ScopedBy
attribute like so.
#[ScopedBy([AgeScope::class, CountryScope::class])]
So, now you don’t need to define the booted
method in the model to attach the global scope. You can do it directly using the ScopedBy
attribute.
In closing
I think Attributes help reduce a lot of boilerplate code and make the code more readable and maintainable. I love how Laravel is embracing PHP 8’s new features and making the development experience even better.
Like this article?
Buy me a coffee👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.