Wiz0TM
157
6
Laravel

Eloquent CRUD Logging Solution

Posted 5 days ago by Wiz0TM

I have tried to create an Eloquent CUD logging silution for a legacy app. I'm stuck with Laravel 5.4.* due to legacy reasons and consequent policy decision. I tried to implement a part of the solution using Model events. Let me present some of the code...

<?php

namespace App\Traits;

use Illuminate\Support\Facades\Auth;
use App\UILog;

trait LoggingTrait
{
    protected function ui_log($source, $event, $payload, $json = TRUE)
    {
        $r = FALSE;
        $request = request();

        if($json)
            $payload = json_encode($payload);

        $new = [
            'created_by' => Auth::user()->name,
            'source' => $source,
            'event' => $event,
            'payload' => $payload
        ];

        try {
            $ui_log = UILog::create($new);
            $r = TRUE;
        } catch(\Exception $e) {
            $request->session()->flash('error_message', 'Error writing to UI Log: ' . $e->getMessage());
        }

        return $r;
    }
}

<?php

namespace App\Events;

use Illuminate\Queue\SerializesModels;

abstract class ModelEvent
{
    use SerializesModels;

    public $event_name;
    public $model_name;
    public $payload;

    /**
     * Create a new event instance.
     *
     * @param  string  $event_name
     * @param  string  $model_name
     * @param  string  $payload
     * @return void
     */
    public function __construct($event_name, $model_name, $payload)
    {
        $this->event_name = $event_name;
        $this->model_name = substr(strrchr($model_name, '\'), 1);
        $this->payload = $payload;
    }
}

<?php

namespace App\Events;

use App\LoggingModel;

class ModelCreated extends ModelEvent
{
    /**
     * Create a new event instance.
     *
     * @param  LoggingModel  $model
     * @return void
     */
    public function __construct(LoggingModel $model)
    {
        parent::__construct('Created', $model->model_name, $model->payload);
    }
}

<?php

namespace App\Listeners;

use App\Events\ModelEvent;
use App\Traits\LoggingTrait;

class LoggingModelEventSubscriber
{
    use LoggingTrait;

    /**
     * Handle LoggingModel events.
     *
     * @param  \App\Events\ModelEvent\ModelEvent  $events
     */
    public function handleLoggingModelEvent(ModelEvent $event) {
        $this->ui_log($event->model_name, $event->event_name, $event->payload);
    }

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'App\Events\ModelCreated',
            static::class . '@handleLoggingModelEvent'
        );

        $events->listen(
            'App\Events\ModelDeleted',
            static::class . '@handleLoggingModelEvent'
        );

        $events->listen(
            'App\Events\ModelSaved',
            static::class . '@handleLoggingModelEvent'
        );

        $events->listen(
            'App\Events\ModelUpdated',
            static::class . '@handleLoggingModelEvent'
        );
    }
}

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use App\Events\ModelCreated;
use App\Events\ModelDeleted;
use App\Events\ModelSaved;
use App\Events\ModelUpdated;

abstract class LoggingModel extends Model
{
    use Notifiable;

    /**
     * The event map for the model.
     *
     * @var array
     */
    protected $events = [
        'created' => ModelCreated::class,
        'deleted' => ModelDeleted::class,
        'saved' => ModelSaved::class,
        'updated' => ModelUpdated::class,
    ];

    public $model_name;

    public $event_payload;

    /**
     * Create a new model instance.
     *
     * @return void
     */
    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->model_name = get_class($this);
    }

    /**
     * Mutator for payload attribute.
     *
     * @param  array  $value
     * @return void
     */
    protected function setPayloadAttribute($value)
    {
        $this->event_payload = $value;
    }

    /**
     * Overridable. Accessor for payload attribute.
     *
     * @return array
     */
    protected function getPayloadAttribute()
    {
        return $this->event_payload ?? ['id' => $this->id];
    }
}

The thing is, this works fine for all of the cases where I'm setting the payload by overriding in derived models only, or the case where I update after setting the payload attribute in the controller before model save(). But when I try to set the payload when calling create () in controller, only the base model's payload is logged, not the value I'm passing to create(). By all accounts, based on the docs and online examples and tutorials, the create() should set the payload then trigger the proper events. However this apparently isn't working as expected (by me). If this issue is specific to older versions of Laravel, I'd like to know. Anyhow, any pointers are welcome...

$request['payload'] = ['country' => 'UK'];

$item = Service::on('connection_a')->create($request->only('some_field_1', 'some_field_2', 'payload'));

Note: I'm not supposed to mess with the legacy code too much, esp. the controllers. I'd like to keep my interventions minimally invasive there. Anyhow, since I've already written my new code this way, I'd like to see if I can get this working with as little modifications as possible, while not missing any glaring pitfalls.

Thanks everyone :-)

Please sign in or create an account to participate in this conversation.

Reply to

Use Markdown with GitHub-flavored code blocks.