PHPackages                             philiprehberger/laravel-model-diff - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. philiprehberger/laravel-model-diff

ActiveLibrary[Database &amp; ORM](/categories/database)

philiprehberger/laravel-model-diff
==================================

Track and display structured differences between Eloquent model versions with human-readable labels

v1.2.0(3mo ago)142MITPHPPHP ^8.2CI passing

Since Mar 9Pushed 1mo agoCompare

[ Source](https://github.com/philiprehberger/laravel-model-diff)[ Packagist](https://packagist.org/packages/philiprehberger/laravel-model-diff)[ Docs](https://github.com/philiprehberger/laravel-model-diff)[ RSS](/packages/philiprehberger-laravel-model-diff/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (16)Versions (8)Used By (0)

Laravel Model Diff
==================

[](#laravel-model-diff)

[![Tests](https://github.com/philiprehberger/laravel-model-diff/actions/workflows/tests.yml/badge.svg)](https://github.com/philiprehberger/laravel-model-diff/actions/workflows/tests.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/78de63b176854edf17338ab92a7d72be9b58e708351fd0b551f611338fb1d430/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7068696c69707265686265726765722f6c61726176656c2d6d6f64656c2d646966662e737667)](https://packagist.org/packages/philiprehberger/laravel-model-diff)[![Last updated](https://camo.githubusercontent.com/dfa10d1cbde4f44093e7c0210143c358e7ed56c22156854026e749a57fc7f394/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f7068696c69707265686265726765722f6c61726176656c2d6d6f64656c2d64696666)](https://github.com/philiprehberger/laravel-model-diff/commits/main)

Track and display structured differences between Eloquent model versions with human-readable labels.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11 or 12

Installation
------------

[](#installation)

Install via Composer:

```
composer require philiprehberger/laravel-model-diff
```

The service provider and facade are registered automatically via Laravel package auto-discovery.

### Publishing the config

[](#publishing-the-config)

```
php artisan vendor:publish --tag=model-diff-config
```

This creates `config/model-diff.php` in your application.

### Configuration

[](#configuration)

```
// config/model-diff.php

return [

    /*
     | Attributes excluded from every diff comparison.
     */
    'ignored_attributes' => [
        'created_at',
        'updated_at',
        'id',
    ],

    /*
     | Format string used when rendering date/datetime values in
     | DiffResult::toHumanReadable().
     */
    'date_format' => 'M j, Y g:i A',

];
```

Usage
-----

[](#usage)

### Comparing two model instances

[](#comparing-two-model-instances)

Pass two instances of the same model — a "before" snapshot and an "after" snapshot — to `ModelDiff::compare()`:

```
use PhilipRehberger\ModelDiff\Facades\ModelDiff;

$before = User::find(42);
// ... some time passes, the record is updated ...
$after = User::find(42);

$result = ModelDiff::compare($before, $after);

if ($result->hasChanges()) {
    // ['name', 'email']
    $result->changedAttributes();

    // Array of AttributeChange objects
    $result->getChanges();

    // Plain arrays
    $result->toArray();

    // Keyed by human-readable label
    $result->toHumanReadable();
}
```

### Comparing an unsaved dirty model

[](#comparing-an-unsaved-dirty-model)

Use `ModelDiff::fromDirty()` to inspect changes on a model that has not yet been saved:

```
$user = User::find(42);
$user->name  = 'New Name';
$user->email = 'new@example.com';

// Do NOT call save() — inspect the dirty state
$result = ModelDiff::fromDirty($user);

$result->changedAttributes(); // ['name', 'email']
```

### Excluding extra attributes at call-site

[](#excluding-extra-attributes-at-call-site)

```
$result = ModelDiff::ignoring(['internal_notes', 'cache_key'])
    ->compare($before, $after);
```

### Filtering results

[](#filtering-results)

Use `only()` and `except()` to narrow a `DiffResult` after comparison:

```
$result = ModelDiff::compare($before, $after);

// Keep only specific attributes
$nameAndEmail = $result->only(['name', 'email']);

// Exclude specific attributes
$withoutMetadata = $result->except(['metadata', 'internal_notes']);
```

### Accessing individual values

[](#accessing-individual-values)

Retrieve the old or new value of a single attribute without iterating:

```
$result = ModelDiff::compare($before, $after);

$result->getBefore('name'); // 'Alice'
$result->getAfter('name');  // 'Bob'

// Returns null when the attribute is not in the diff
$result->getBefore('unchanged_field'); // null
```

### Human-Readable Labels

[](#human-readable-labels)

#### Using the HasDiffLabels trait

[](#using-the-hasdifflabels-trait)

Add the `HasDiffLabels` trait to any model and define a `$diffLabels` map:

```
use PhilipRehberger\ModelDiff\Concerns\HasDiffLabels;

class Client extends Model
{
    use HasDiffLabels;

    protected array $diffLabels = [
        'company_name' => 'Company Name',
        'is_active'    => 'Active Status',
        'arr_monthly'  => 'Monthly ARR',
    ];
}
```

Attributes without an explicit entry are automatically humanized: `billing_address` becomes `Billing Address`.

#### Retrieving a label directly

[](#retrieving-a-label-directly)

```
$client = new Client();
$client->getDiffLabel('company_name'); // "Company Name"
$client->getDiffLabel('phone_number'); // "Phone Number"
```

API
---

[](#api)

### `DiffResult`

[](#diffresult)

MethodReturn typeDescription`hasChanges()``bool``true` when at least one attribute changed`changedAttributes()``string[]`Names of changed attributes`getChanges()``AttributeChange[]`All change objects`only(string[] $attributes)``DiffResult`New result with only the specified attributes`except(string[] $attributes)``DiffResult`New result excluding the specified attributes`getBefore(string $attribute)``mixed`Old value for an attribute (`null` if not in diff)`getAfter(string $attribute)``mixed`New value for an attribute (`null` if not in diff)`toArray()``array`Plain array — one entry per change`toHumanReadable()``array`Keyed by label; values formatted for display### toArray() output

[](#toarray-output)

```
[
    [
        'attribute' => 'name',
        'old'       => 'Alice',
        'new'       => 'Bob',
        'label'     => 'Full Name',
    ],
    // ...
]
```

### toHumanReadable() output

[](#tohumanreadable-output)

```
[
    'Full Name' => [
        'old' => 'Alice',
        'new' => 'Bob',
    ],
    'Published At' => [
        'old' => 'Jan 1, 2024 9:00 AM',
        'new' => 'Jun 20, 2025 2:30 PM',
    ],
    // ...
]
```

### `AttributeChange`

[](#attributechange)

PropertyTypeDescription`$attribute``string`Raw attribute name`$old``mixed`Normalized old value`$new``mixed`Normalized new value`$label``string`Human-readable label```
foreach ($result->getChanges() as $change) {
    echo "{$change->label}: {$change->old} → {$change->new}";
}
```

### Cast-Aware Comparison

[](#cast-aware-comparison)

The package normalizes values before comparing them, so you never get false positives from type mismatches:

Cast typeNormalization`date`, `datetime`, `immutable_date/datetime`Parsed to Carbon and formatted with `date_format` config`timestamp`Parsed to Carbon and formatted with `date_format` config`array`, `json`, `object`, `collection`Decoded and compared by content, not by serialized string`boolean`, `bool`Strict `(bool)` cast before comparison`integer`, `int`Strict `(int)` cast`float`, `double`, `real`Strict `(float)` cast`decimal:N`Strict `(float)` castBacked enum (`SomeEnum::class`)Compared by `->value`; stored as scalar in `AttributeChange`Unit enum (`SomeEnum::class` without backing)Compared by `->name`; stored as string in `AttributeChange`> **Note:** Associative arrays are compared order-insensitively — `['a' => 1, 'b' => 2]` equals `['b' => 2, 'a' => 1]`. Sequential (list) arrays are compared in order.

### Using the Facade

[](#using-the-facade)

The `ModelDiff` facade is registered automatically:

```
use PhilipRehberger\ModelDiff\Facades\ModelDiff;

$result = ModelDiff::compare($before, $after);
$result = ModelDiff::fromDirty($model);
$result = ModelDiff::ignoring(['token'])->compare($before, $after);
```

### Using the Class Directly

[](#using-the-class-directly)

If you prefer not to use the facade, resolve the class from the container or instantiate it directly:

```
use PhilipRehberger\ModelDiff\ModelDiff;

// Via DI
public function __construct(private ModelDiff $diff) {}

// Directly
$diff = new ModelDiff();
$result = $diff->compare($before, $after);
```

Development
-----------

[](#development)

```
composer install
vendor/bin/phpunit
vendor/bin/pint --test
vendor/bin/phpstan analyse
```

Support
-------

[](#support)

If you find this project useful:

⭐ [Star the repo](https://github.com/philiprehberger/laravel-model-diff)

🐛 [Report issues](https://github.com/philiprehberger/laravel-model-diff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)

💡 [Suggest features](https://github.com/philiprehberger/laravel-model-diff/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)

❤️ [Sponsor development](https://github.com/sponsors/philiprehberger)

🌐 [All Open Source Projects](https://philiprehberger.com/open-source-packages)

💻 [GitHub Profile](https://github.com/philiprehberger)

🔗 [LinkedIn Profile](https://www.linkedin.com/in/philiprehberger)

License
-------

[](#license)

[MIT](LICENSE)

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance87

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 95.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

7

Last Release

96d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/cfd7d24cbbf32400fa13ce0bbe7a31edd2d66a6d4488eafdb3d64c5337bf0435?d=identicon)[philiprehberger](/maintainers/philiprehberger)

---

Top Contributors

[![philiprehberger](https://avatars.githubusercontent.com/u/8218077?v=4)](https://github.com/philiprehberger "philiprehberger (23 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

difflaravelmodeleloquentAuditchangelog

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/philiprehberger-laravel-model-diff/health.svg)

```
[![Health](https://phpackages.com/badges/philiprehberger-laravel-model-diff/health.svg)](https://phpackages.com/packages/philiprehberger-laravel-model-diff)
```

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.0M88](/packages/mongodb-laravel-mongodb)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

591452.8k2](/packages/spiritix-lada-cache)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.1M337](/packages/psalm-plugin-laravel)[watson/validating

Eloquent model validating trait.

9733.4M53](/packages/watson-validating)[cybercog/laravel-love

Make Laravel Eloquent models reactable with any type of emotions in a minutes!

1.2k322.4k1](/packages/cybercog-laravel-love)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
