Alan
Alan

Reputation: 2639

Laravel multiple listeners for a model event

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

Answers (2)

patricus
patricus

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

Saumya Rastogi
Saumya Rastogi

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

Related Questions