Cleaner code with Eloquent events
As part of a current side-project, I needed to “protect” certain model attributes, to ensure that, once saved, they do not change. This is a pretty common requirement, and in the past I’ve seen it implemented as follows.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
public function save(array $options = [])
{
if ($this->exists) {
$this->uuid = $this->getOriginal('uuid');
}
parent::save();
}
}
This works, but thanks to Eloquent’s lifecycle events, there is a much better option.
Eloquent lifecycle events
Eloquent makes it easy to respond to certain key points in the lifecycle of a model instance, by exposing events such as creating
, updating
, and deleted
.
Protecting an attribute
Rather than overriding the built-in save
method, and checking if the model already exists, you can simply hook into the updating
event:
public static function boot()
{
parent::boot();
static::updating(function ($instance) {
$instance->uuid = $instance->getOriginal('uuid');
});
}
Protecting all attributes
It’s even easier if you want to make your model instance immutable; just return false
from the event listener.
public static function boot()
{
parent::boot();
static::updating(function ($instance) {
return false;
});
}
Keeping things clean
The “callback” approach works fine if you’re doing something very simple, but generally speaking, I’m not a fan. Instead, I prefer to move my model event listener code to a separate “observer” class.
<?php
namespace App\Observers;
use App\Project;
class ProjectObserver
{
public function updating(Project $instance)
{
$instance->uuid = $instance->getOriginal('uuid');
}
}
And then register that observer in the model’s boot
method. The example in the Laravel documentation uses the AppServiceProvider::boot
method, but I much prefer to register my observers in the model. That way, it’s at least obvious that something is happening in response to one or more lifecycle events.
public static function boot()
{
parent::boot();
static::observe(new \App\Observers\ProjectObserver);
}
Observing multiple events
One of the nice things about observers, aside from the fact they just look neater than callbacks, is that they can respond to as many model events as you need.
Just add a public method named after the event, and you’re good to go:
<?php
namespace App\Observers;
use App\Project;
use Ramsey\Uuid\Uuid;
class ProjectObserver
{
public function creating(Project $instance)
{
$instance->uuid = Uuid::uuid4()->toString();
}
public function updating(Project $instance)
{
$instance->uuid = $instance->getOriginal('uuid');
}
}
Sign up for my newsletter
A monthly round-up of blog posts, projects, and internet oddments.