Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Eloquent Modification Laravel Package

dicibi/eloquent-modification

A Laravel package for tracking changes to Eloquent models, helping you record and review modifications made to your data over time. Integrates with existing models to keep an audit trail of edits.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the package assets, then run the migrations:

composer require dicibi/eloquent-modification
php artisan vendor:publish --tag="eloquent-modification-config"   # check README for exact tag
php artisan vendor:publish --tag="eloquent-modification-migrations"
php artisan migrate

Verify the tag names against the README on the v0.0.3 tag — at 0.0.x the publish tags and file names have not stabilised.

Where to look first, in order:

  1. README.md — the trait name and the two or three lines of usage example. This is the fastest way to confirm the package's core abstraction.
  2. config/eloquent-modification.php (after publishing) — table name, the model class used to persist rows, and any "except"/ignored-attribute list. This config drives nearly every customisation you will want.
  3. src/ — the trait (typically HasModifications) and the Modification Eloquent model. Read the trait's boot…() method to see exactly which model events are hooked.
  4. database/migrations/ — the schema, so you know which columns you can query and index.

First use case — drop the trait onto a model and exercise it:

use Dicibi\EloquentModification\Concerns\HasModifications;

class Post extends Model
{
    use HasModifications;
}

$post = Post::create(['title' => 'Draft']);
$post->update(['title' => 'Final']);

$post->modifications()->latest()->first();  // inspect the recorded change

If that returns a row with the previous and new values, the package is wired correctly and everything else is configuration.

Implementation Patterns

Reading history in a controller. The relation returned by the trait is the primary API. Eager-load it to avoid N+1 when listing several records:

$posts = Post::with(['modifications' => fn ($q) => $q->latest()->limit(10)])->paginate();

Render a diff by iterating the changed-attributes payload, and filter out noise (updated_at, hidden columns) before display.

Attributing changes to a user. The package almost certainly resolves the actor from the authenticated user. For anything outside an HTTP request — queued jobs, Artisan commands, seeders — there is no session user, so set the causer explicitly (check the README for the exact helper; commonly a static setter on the Modification model or a config-resolved callback). Wrap job payload handling with it rather than relying on Auth::id() being non-null.

Bulk writes. $query->update([...]), insert(), upsert(), truncate(), and raw statements do not fire model events, so they produce no history. Where history matters, chunk and save() each model:

Post::where('status', 'draft')->chunkById(500, function ($posts) {
    foreach ($posts as $post) {
        $post->update(['status' => 'archived']);   // logged
    }
});

Where it doesn't matter, accept the gap deliberately and document it — a partial audit trail that looks complete is worse than none.

Selective auditing. Rather than excluding sensitive columns globally, prefer a per-model contract: a $modificationExcept property (or the config except array) covering password, remember_token, API keys, and anything encrypted. Combine with $hidden awareness so diffs never leak into an API response.

Retention. Modification tables grow fast — one row per touched attribute set per save. Add a scheduled prune against the package's model (Modification::where('created_at', '<', now()->subMonths(6))->delete() or the model's prune support) and an index on (modifiable_type, modifiable_id, created_at).

Testing. Use RefreshDatabase, assert with assertDatabaseHas('modifications', [...]), and consider an env-driven kill switch in config so unrelated feature tests don't accumulate history rows and slow down.

Gotchas and Tips

  • This is a 0.0.x package. Pin it exactly ("dicibi/eloquent-modification": "0.0.3") and read the diff before any bump — pre-1.0 packages are free to break the trait signature or column layout in a patch release. v0.0.3 itself contains only Dependabot bumps to dependabot/fetch-metadata and a dependency upgrade PR, so there is no API change and no migration step; it is a safe no-op upgrade.
  • Silent bypasses. saveQuietly(), withoutEvents(), and anything inside Model::withoutEvents(fn () => ...) will skip logging. If a critical update path lives in a job, add a test that asserts history was written.
  • Auditing the auditor. If you extend or swap the Modification model, make sure it does not itself use the trait — the obvious symptom is duplicate rows or unbounded recursion.
  • Cast mismatch in diffs. Stored values are typically the raw DB representation, while your model's casts (datetime, enum, array, AsEnumCollection) return objects on read. Cast old/new values back before comparing or rendering, or a "no change" diff will appear non-empty for dates.
  • Column renames break history. Old rows keep the old attribute keys. Keep a lookup map of old_name => new_name in your presenter so historical entries still render.
  • Transactions and throughput. Each save becomes two writes. On hot tables this doubles insert volume and can surface row-lock waits; watch for that before rolling it out across every model.
  • Config cache. After editing the config, run php artisan config:clear (and queue:restart for workers). A stale cached config is the most common cause of "the table name is wrong" / "the actor is always null".
  • Debugging. Temporarily disable the package via config/env and confirm the behaviour disappears, to isolate whether a bug is yours or the package's. Tail the modifications table directly (Model::latest('id')->first()) rather than stepping through observers.
  • Extension points to prefer: a custom model class via config (modification_model) for adding scopes and accessors; a custom causer resolver for multi-guard apps; and the Modification model's own Eloquent events for downstream notifications — all less brittle than overriding trait methods, which are the most likely thing to change between 0.0.x releases.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console