I have a trait called RecordsUserActivity which basically create a record in an activities table when a User creates, updates or deletes something that uses this trait. Here is the code:
trait RecordsUserActivity
{
protected static function boot()
{
parent::boot();
foreach (static::getModelEvents() as $event) {
static::$event(function ($model) use ($event) {
$model->addActivity($event);
});
}
}
protected function addActivity($event)
{
$newActivity = [
'subject_id' => $this->id,
'subject_type' => get_class($this),
'action' => $event,
'user_id' => (Auth::id()) ?? null,
];
if ($event == 'updated') {
$newActivity['data'] = json_encode($this->getDirty());
}
UserActivity::create($newActivity);
}
protected static function getModelEvents()
{
if (isset(static::$recordEvents)) {
return static::$recordEvents;
}
return ['created', 'deleted', 'updated'];
}
}
Then I have another trait which records property changes in models that use it. Here is the code:
trait RecordsPropertyChangelog
{
protected static function boot()
{
parent::boot();
static::updated(function ($model){
$model->addPropertiesChangelog();
});
}
protected function addPropertiesChangelog()
{
$dirty = $this->getDirty();
foreach ($dirty as $field => $newData) {
$oldData = $this->getOriginal($field);
$this->addPropertyChangelog($field,$oldData,$newData);
}
}
protected function addPropertyChangelog($fieldName,$oldValue,$newValue)
{
PropertyChangelog::create([
'resource_id' => $this->id,
'resource_type' => get_class($this),
'property' => $fieldName,
'from_value' => $oldValue,
'to_value' => $newValue,
'data' => '{}',
]);
}
}
The problem appears when I include both traits in a model and an update is made, there is some kind of collision with both updated model events. Is there some way to fix this or should I find out another solution?
If you attempt to use both of those traits on the same model, you should be getting an error stating Trait method boot has not been applied, because there are collisions with other trait methods...
.
In any event, you don't want your traits to define the boot()
method. Laravel's Model
has a special convention if you have a trait that needs to hook into the boot
method. Basically, in your trait, define a method in the format of boot{traitName}
. Additionally, remove the call to parent::boot()
in both methods. The boot()
method on the base Model
will call methods that conform to this format when the Model
is booted.
So, your traits should look like:
trait RecordsUserActivity
{
protected static function bootRecordsUserActivity()
{
foreach (static::getModelEvents() as $event) {
static::$event(function ($model) use ($event) {
$model->addActivity($event);
});
}
}
//...
}
trait RecordsPropertyChangelog
{
protected static function bootRecordsPropertyChangelog()
{
static::updated(function ($model) {
$model->addPropertiesChangelog();
});
}
//...
}