fico7489/laravel-revisionable-upgrade
Laravel package for adding revision history to Eloquent models. It tracks changes, stores model snapshots, and lets you view diffs or restore previous versions—an upgraded alternative to older revisionable packages.
Install both packages. The upgrade package is an extension, not a replacement:
composer require venturecraft/revisionable fico7489/laravel-revisionable-upgrade
If Composer complains about version constraints on newer Laravel releases, pin the upgrade to the latest tagged release and allow it via --with-all-dependencies, or add an inline "repositories" / platform override. Treat it as a legacy dependency.
Publish and run the revisionable migration. The upgrade package does not ship its own schema; it reuses the revisions table from venturecraft. Run php artisan vendor:publish --provider="Venturecraft\Revisionable\RevisionableServiceProvider" then php artisan migrate.
Swap the trait on your model. Replace venturecraft's trait with the upgrade trait (it extends the original behaviour):
use Fico7489\LaravelRevisionableUpgrade\Traits\RevisionableUpgradeTrait;
class Post extends Model
{
use RevisionableUpgradeTrait;
protected $revisionable = ['title', 'body', 'status'];
}
Look first at the trait source (vendor/fico7489/laravel-revisionable-upgrade/src/Traits/RevisionableUpgradeTrait.php) and the README's method list. The whole package is a thin set of helpers around venturecraft\Revisionable\RevisionableTrait; reading both traits side-by-side is faster than guessing at method signatures.
First use case: render a per-record audit trail and let an editor roll back a bad change:
$post->getRevisionHistory(); // collection of Revision rows
$post->getRevisionByVersion(3); // specific snapshot
$post->revertToVersion(3); // writes a new revision restoring v3
Audit-log page (per record). Load history once and join the user relation rather than per-row:
$history = $post->getRevisionHistoryWithUser();
// or eager-load users when you have many records
Post::with('revisionHistory.user')->paginate();
Render revision->created_at, revision->user->name, and the field deltas. Use the revision's own updated_at as the "changed at" timestamp—don't fall back to the model's timestamp.
Version comparison / diff view. The upgrade's value is in the diff helpers. Typical pattern:
$old = $post->getRevisionByVersion($fromVersion);
$new = $post->getRevisionByVersion($toVersion);
$diff = $post->getRevisionDiff($old, $new); // structured old/new pairs
Iterate the diff array into a two-column table. Always guard against null revisions (an unchanged field is simply absent, not an empty string), and treat HTML/JSON columns separately—render them escaped or pretty-printed rather than line-diffing.
Revert workflow. Wrap reverts in a transaction, authorize first, and log who triggered it:
DB::transaction(function () use ($post, $version) {
$post->revertToVersion($version);
activity()->on($post)->log("Reverted to v{$version}");
});
Because reverts create a new revision, the operation is itself auditable and idempotent-ish; you never destroy history.
Approval / draft flows. Use getRevisionByDate() when business rules are time-based ("what did this look like at contract signing?"), and getRevisionByVersion() when they're sequence-based ("restore the pre-review draft"). Pin the version number in the workflow record so it survives later edits.
Custom user attribution. If your auth is multi-tenant or uses an API token (no Auth::user()), override the user resolution on the model so revisions don't end up with null user IDs. The venturecraft side exposes this via a resolver method; keep it consistent across all revisionable models.
Displaying the "current" vs "historic". Snapshot models are read-only—don't save them through the normal path, or you'll silently create a new revision on the live row. Expose them as DTOs or a read-only presenter.
owen-it/laravel-auditing or fico7489's own laravel-revisionable fork.RevisionableTrait—using both will conflict. If some models need only vanilla behaviour, keep them on venturecraft and put the upgrade trait only where you use versioning/diff helpers.getRevisionHistoryWithUser() and diff methods may silently return empty joins. Check the revisions table for user_id, ip_address, and the morph columns.id for that, and use version only against a specific model instance.Relation::morphMap([...]), revisionable_type stores the alias, not the FQCN. Any code that queries revisions by raw class name will miss rows after a morph-map change.Revision instances (or vice versa) depending on the tag. Verify against the trait source at the installed version—ReflectionMethod or a quick dump() in tinker is quicker than a Stack Overflow rabbit hole.$revisionable is opt-in per field. A field not listed is never recorded; a field added later has no history before that release. Document this for support teams or they'll file "missing history" tickets.deleted_at explicitly in the revert wrapper if your workflow needs it.getRevisionHistory() on a collection triggers N+1 queries. Eager-load revisionHistory and its user, and consider a dedicated index on (revisionable_type, revisionable_id, version) for large audit tables.getRevisionModel() override) if you need extra columns or scopes; keep the trait untouched so upgrades remain diffable.revisions row immediately after a save; if it's empty, verify the field is in $revisionable, the attribute actually changed (dirty check), and revisionable isn't disabled globally in config.How can I help you explore Laravel packages today?