anonimowybanan/model-diff

Laravel package for tracking and comparing Eloquent model changes

Maintainers

Package info

github.com/AnonimowyBanan/ModelDiff

pkg:composer/anonimowybanan/model-diff

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-04-17 15:37 UTC

This package is auto-updated.

Last update: 2026-04-17 15:39:39 UTC


README

Latest Version on Packagist Total Downloads License

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.

Requirements

  • PHP 8.1+
  • Laravel 10+

Installation

You can install the package via Composer:

composer require anonimowybanan/model-diff

Publish the config file and migration:

php artisan vendor:publish --provider="AnonimowyBanan\ModelDiff\ModelDiffServiceProvider"

Run the migration:

php artisan migrate

Quick Start

Add 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' field

Usage

Comparing Changes

Compare 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);

Querying Change History

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);

Query Scopes

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

Output Formatting

$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']]

Manual Logging

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']);

Batch Tracking

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; // true

Configuration

After publishing, the config file is located at config/model-diff.php.

Global Ignored Fields

Fields that are never tracked across all models:

'ignored_fields' => ['password', 'remember_token', 'updated_at', 'created_at'],

Auto Logging

Toggle automatic change logging on model save:

'auto_log' => true,

User Resolver

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,

Per-Model Configuration

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'],
    ],
],

Trait-Level Overrides

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
    }
}

Custom Table Name

Change the database table used for storing change logs:

'table_name' => 'model_diff_change_logs',

Testing

composer test

License

The MIT License (MIT). Please see License File for more information.