Track and compare Eloquent model changes with ease. ModelDiff automatically logs field-level modifications, supports batched change tracking, and provides a powerful query API for browsing change history.
- PHP 8.1+
- Laravel 10+
You can install the package via Composer:
composer require anonimowybanan/model-diffPublish the config file and migration:
php artisan vendor:publish --provider="AnonimowyBanan\ModelDiff\ModelDiffServiceProvider"Run the migration:
php artisan migrateAdd the TracksChanges trait to any Eloquent model you want to track:
use AnonimowyBanan\ModelDiff\Traits\TracksChanges;
class User extends Model
{
use TracksChanges;
}That's it. Every time the model is updated, changes are automatically logged to the database.
$user = User::create(['name' => 'John', 'email' => 'john@example.com']);
$user->update(['name' => 'Jane']);
// A change log entry is now stored for the 'name' fieldCompare the current dirty state of a model against its persisted state:
$user = User::find(1);
$user->name = 'Jane';
$diff = $user->getDiff();
if ($diff->hasChanges()) {
$diff->changedFields(); // ['name']
$change = $diff->fieldChange('name');
$change->oldValue; // 'John'
$change->newValue; // 'Jane'
}You can also compare two separate model instances:
use AnonimowyBanan\ModelDiff\Facades\ModelDiff;
$diff = ModelDiff::compareModels($oldUser, $newUser);use AnonimowyBanan\ModelDiff\Facades\ModelDiff;
// All changes for a model (newest first)
$history = ModelDiff::historyFor($user)->get();
// Changes grouped by batch (all fields changed in a single save)
$grouped = ModelDiff::groupedHistoryFor($user);The ChangeLog model provides several useful scopes:
use AnonimowyBanan\ModelDiff\Models\ChangeLog;
ChangeLog::forModel($user)->get(); // Changes for a specific model
ChangeLog::forModel($user)->forField('name')->get(); // Changes for a specific field
ChangeLog::byUser($userId)->get(); // Changes made by a specific user
ChangeLog::inBatch($batchId)->get(); // Changes from a specific batch$diff = $user->getDiff();
// Array format
$diff->toArray();
// [['field' => 'name', 'old' => 'John', 'new' => 'Jane']]
// Table format (useful for Artisan commands)
$diff->toTable();
// [['Field', 'Old Value', 'New Value'], ['name', 'John', 'Jane']]If you prefer to log changes manually, disable auto-logging and call logDiff yourself:
config(['model-diff.auto_log' => false]);
$diff = app('model-diff')->compare($user);
app('model-diff')->logDiff($user, $diff, ['reason' => 'Admin override']);When multiple fields change in a single update(), they share the same batch UUID:
$user->update(['name' => 'Jane', 'email' => 'jane@example.com']);
$logs = ChangeLog::forModel($user)->get();
$logs[0]->batch === $logs[1]->batch; // trueAfter publishing, the config file is located at config/model-diff.php.
Fields that are never tracked across all models:
'ignored_fields' => ['password', 'remember_token', 'updated_at', 'created_at'],Toggle automatic change logging on model save:
'auto_log' => true,Customize how the current user is resolved for change attribution:
'user_resolver' => fn () => auth()->id(),
// Or use a callable class
'user_resolver' => App\Services\CustomUserResolver::class,Define tracked or ignored fields on a per-model basis:
'models' => [
// Whitelist: track ONLY these fields
App\Models\User::class => [
'only' => ['name', 'email'],
],
// Blacklist: ignore these fields (in addition to global ignored)
App\Models\Post::class => [
'ignored' => ['view_count', 'cache_key'],
],
],Override tracked or ignored fields directly on the model:
class User extends Model
{
use TracksChanges;
public function getTrackedFields(): ?array
{
return ['name', 'email']; // Only track these fields
}
public function getIgnoredFields(): array
{
return ['api_token']; // Ignore in addition to global list
}
}Change the database table used for storing change logs:
'table_name' => 'model_diff_change_logs',composer testThe MIT License (MIT). Please see License File for more information.