Last active
September 20, 2023 06:59
-
-
Save barryo/a6e93c0443327143188656aab6551bc0 to your computer and use it in GitHub Desktop.
Logging Changes in Laravel Eloquent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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'); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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, | |
] | |
]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 | |
); | |
} | |
} |
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();
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
Hello,
I have a problem.
I think it comes from my inherited structure:
Do you have an idea?
Thank you