Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Logging Changes in Laravel Eloquent
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLogTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->nullable();
$table->string('model',100)->index();
$table->string('action',7);
$table->text('message');
$table->json('models');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('xibn_users');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('logs');
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* App\Log
*
* @property int $id
* @property int $user_id
* @property string $action
* @property string $message
* @property array $models
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log query()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereAction($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereMessage($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereModels($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Log whereUserId($value)
* @mixin \Eloquent
*/
class Log extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'logs';
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'models' => 'array',
];
/**
* Get the user record associated with this log
*/
public function user()
{
return $this->hasOne(User::class);
}
}
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use App\Log;
use Illuminate\Support\Facades\Auth;
/**
* Observable trait
*
* @package App\Traits
*/
trait Observable
{
public static function bootObservable()
{
static::saved(function (Model $model) {
// create or update?
if( $model->wasRecentlyCreated ) {
static::logChange( $model, 'CREATED' );
} else {
if( !$model->getChanges() ) {
return;
}
static::logChange( $model, 'UPDATED' );
}
});
static::deleted(function (Model $model) {
static::logChange( $model, 'DELETED' );
});
}
/**
* String to describe the model being updated / deleted / created
*
* Override this in your own model to customise - see below for example
*
* @return string
*/
public static function logSubject(Model $model): string {
return static::logImplodeAssoc($model->attributesToArray());
}
/**
* Format an assoc array as a key/value string for logging
* @return string
*/
public static function logImplodeAssoc(array $attrs): string {
$l = '';
foreach( $attrs as $k => $v ) {
$l .= "{ $k => $v } ";
}
return $l;
}
/**
* String to describe the model being updated / deleted / created
* @return string
*/
public static function logChange( Model $model, string $action ) {
Log::create([
'user_id' => Auth::check() ? Auth::user()->id : null,
'model' => static::class,
'action' => $action,
'message' => static::logSubject($model),
'models' => [
'new' => $action !== 'DELETED' ? $model->getAttributes() : null,
'old' => $action !== 'CREATED' ? $model->getOriginal() : null,
'changed' => $action === 'UPDATED' ? $model->getChanges() : null,
]
]);
}
}
<?php
namespace App;
use App\Traits\Observable;
// ...
class User extends Authenticatable
{
// ...
use Observable;
/**
* String to describe the model being updated / deleted / created
* @param Model $model
* @return string
*/
public static function logSubject(Model $model): string
{
return sprintf( "User [id:%d] %s/%s",
$model->id, $model->name, $model->email
);
}
}
@piep14
Copy link

piep14 commented Mar 1, 2021

Hello,

I have a problem.

I think it comes from my inherited structure:

array:10 [▼
  "id" => 17
  "user_id" => 17
  "slug" => "test"
  "name" => array:1 [▼
    "fr" => "testdsdsfffs"
  ]
  "content" => array:1 [▼
    "fr" => """
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id lectus vitae lacus imperdiet dignissim sit amet in elit. Cras ultrices magna at neque pelle ▶
      <p>Integer tellus tellus, issssmperdiet condimentum velit vitae, congue malesuada magna. Curabitur cursus non erat in iaculis. In vulputate metus sit amet vehic ▶
      <p>Quisque mollis egestas turpis, vitae pulvinar ex feugiat et. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam non iaculis nisl, a vulputate mi. ▶
      <p> </p>
      <p><img class="content-img" src="http://www.mairie.test/storage/78/avatar.jpg" alt="" width="300" /></p>
      """
  ]
  "published_at" => "2021-02-13T17:31:00.000000Z"
  "enable" => 1
  "created_at" => "2021-02-13T17:34:26.000000Z"
  "updated_at" => "2021-03-01T18:53:22.000000Z"
  "deleted_at" => null
]

Do you have an idea?

Thank you

@alexbleon
Copy link

alexbleon commented Aug 4, 2021

Thank you - cook idea.
I have suggestion for your code:

  • make index on 'model' to allow filter by it. So do follow: $table->string('model',100)->index();

@barryo
Copy link
Author

barryo commented Aug 4, 2021

Thank you - cook idea.
I have suggestion for your code:

* make index on 'model'  to allow filter by it. So do follow: $table->string('model',100)->index();

👍 Done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment