Tuesday, March 7, 2017

How to store log of model changes in laravel?

I have observer trait MainObserver for any model.

<?

namespace App\Observers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use App\Models\AppEvent;
use App\Models\AppEventData;

trait MainObserver
{

    public function handleCreatedEvent($model) {
        $event = new AppEvent;
        $event->event_type = 'CREATE';
        $event->model_type = $model::getModelType();
        $event->model_id = $model->id;
        $event->creator_id = Auth::user() ? Auth::user()->id : null;
        $event->save();
    }

    public function handleUpdatedEvent($model) {
        $originalData = $model->getOriginal();
        $newData = $model->getAttributes();

        $modelType = $model::getModelType();
        $modelFields = $model->getFieldsKeys();

        $event = new AppEvent;
        $event->event_type = 'EDIT';
        $event->model_type = $modelType;
        $event->model_id = $model->id;
        $event->creator_id = Auth::user() ? Auth::user()->id : null;

        $eventDataArray = [];
        foreach($modelFields as $modelAttribute) {

            if(!$model->shouldSaveEventForField($modelAttribute)) continue;

            if(!isset($originalData[$modelAttribute])) {
                $originalData[$modelAttribute] = null;
            }
            if(!isset($newData[$modelAttribute])) {
                $newData[$modelAttribute] = null;
            }

            if($originalData[$modelAttribute] !== $newData[$modelAttribute]) {
                $eventData = new AppEventData;
                $eventData->field = $modelAttribute;
                $fieldData = $model->getField($modelAttribute);

                if($fieldData['type'] === 'password') {
                    $originalData[$modelAttribute] = '***';
                    $newData[$modelAttribute] = '******';
                }

                $eventData->old_value = $originalData[$modelAttribute];
                $eventData->new_value = $newData[$modelAttribute];
                $eventDataArray[] = $eventData;
            }
        }

        if(!count($eventDataArray)) return;

        DB::transaction(function() use($event, $eventDataArray) {
            $event->save();
            $event->eventData()->saveMany($eventDataArray);
        });
    }

}

And it is used like this

<?

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    use MainObserver;

    public function created(User $user) {
        $this->handleCreatedEvent($user);
    }


    public function updated(User $user) {
        $this->handleUpdatedEvent($user);
    }

}

namespace App\Observers;

use App\Models\User;

class UserObserver
{
use MainObserver;

public function created(User $user) {
    $this->handleCreatedEvent($user);
}


public function updated(User $user) {
    $this->handleUpdatedEvent($user);
}




// in AppServiceProvider
User::observe(UserObserver::class);

The problem is: it does not store foreign relations (belongsToMany).

So I wrote this trait for my models

public function setAttribute($key, $value) {
             $pivot = static::getFieldPivotField($key);
             if( $pivot ) { 
                 $this->delayedSave[] = [ 
                     'key' => $key, 'value' => $value, 'pivot' => $pivot
                 ];
                 return;
             }
             return parent::setAttribute($key, $value)
}


public function save(array $options = array()) {
         $exists = $this->exists;
         $dirty = $this->getDirty();
         $saved = parent::save($options);
         if($saved) {
             foreach($this->delayedSave as $v) {
                 $this->savePivot($v['key'], $v['value'], $v['pivot']);
             }
             $this->delayedSave = []; 
         }
}

And, finally, savePivot: it takes old ids and new ids for pivot and store them, if something changed.

private function savePivot($key, $value, $pivot) {
     $ids = array_column($value, 'id');
     $old = $this->{$key}()->get()->pluck('pivot.' . $pivot)->toArray();

     if($old === $ids) return;

     $event = new AppEvent;
     $event->event_type = 'EDIT';
     $event->model_type = $this::getModelType();
     $event->model_id = $this->id;
     $event->creator_id = Auth::user() ? Auth::user()->id : null;

     $eventDataArray = [];

     $eventData = new AppEventData;
     $eventData->field = $key;
     $eventData->old_value = join('|', $old);
     $eventData->new_value = join('|', $ids);
     $eventDataArray[] = $eventData;

     DB::transaction(function() use($event, $eventDataArray, $key, $ids) {
         $this->{$key}()->sync($ids);
         $event->save();
         $event->eventData()->saveMany($eventDataArray);
     });


 }

May be it is too much and there is a simple way to store log of foreign relations updates?



via Jackson J

Advertisement