Reputation: 2639
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?
Upvotes: 2
Views: 3200
Reputation: 62238
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();
});
}
//...
}
Upvotes: 13
Reputation: 13703
According to laravel docs, for managing model events you should use, Laravel Model Observers like this:
<?php
namespace App\Observers;
use App\User;
class UserObserver
{
/**
* Listen to the User created event.
*
* @param User $user
* @return void
*/
public function created(User $user)
{
//
}
/**
* Listen to the User deleting event.
*
* @param User $user
* @return void
*/
public function deleting(User $user)
{
//
}
}
and to register your observers, use the observe method on the model you wish to observe. You may register observers in the boot method of one of your service providers. In this example, we'll register the observer in the AppServiceProvider:
<?php
namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
User::observe(UserObserver::class);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
}
Hope this helps!
Upvotes: -1