Basic Usage
This guide covers the revision tracking system in detail, including configuration options, querying revisions, and reverting changes.
Setting Up a Model
Section titled “Setting Up a Model”Add the HasRevisions trait and implement the Traceable interface:
use Cline\Tracer\Concerns\HasRevisions;use Cline\Tracer\Contracts\Traceable;use Illuminate\Database\Eloquent\Model;
class Article extends Model implements Traceable{ use HasRevisions;
protected $fillable = ['title', 'content', 'status', 'author_id'];}Automatic Tracking
Section titled “Automatic Tracking”Once configured, Tracer automatically tracks:
| Event | Action Recorded | Old Values | New Values |
|---|---|---|---|
created | created | Empty | All tracked attributes |
updated | updated | Changed attributes (old) | Changed attributes (new) |
deleted | deleted | All tracked attributes | Empty |
forceDeleted | force_deleted | All tracked attributes | Empty |
restored | restored | Empty | All tracked attributes |
| Revert | reverted | Current state | Reverted state |
// All these are tracked automatically$article = Article::create(['title' => 'Hello', 'content' => 'World']);// Revision 1: action=created
$article->update(['title' => 'Hello World']);// Revision 2: action=updated, old={title: 'Hello'}, new={title: 'Hello World'}
$article->delete();// Revision 3: action=deletedControlling Tracked Attributes
Section titled “Controlling Tracked Attributes”Configuration is managed via config/tracer.php or runtime registration, not on the model itself.
Via Config File
Section titled “Via Config File”'models' => [ App\Models\Article::class => [ 'tracked_attributes' => ['title', 'content', 'status'], 'untracked_attributes' => ['internal_notes'], ], App\Models\User::class => [ 'untracked_attributes' => ['password', 'api_token'], ],],Via Runtime Registration
Section titled “Via Runtime Registration”use Cline\Tracer\Tracer;
// Track specific attributes onlyTracer::configure(Article::class) ->trackAttributes(['title', 'content', 'status']);
// Exclude specific attributesTracer::configure(User::class) ->untrackAttributes(['password', 'api_token']);Global Untracked Attributes
Section titled “Global Untracked Attributes”Configure in config/tracer.php:
'untracked_attributes' => [ 'id', 'created_at', 'updated_at', 'deleted_at', 'remember_token', 'password', // Add custom global exclusions 'api_token',],Querying Revisions
Section titled “Querying Revisions”Get All Revisions
Section titled “Get All Revisions”// Via relationship (ordered by version descending)$revisions = $article->revisions;
// Via facade$revisions = Tracer::revisions($article)->all();Get Latest Revision
Section titled “Get Latest Revision”$latest = $article->latestRevision();// or$latest = Tracer::revisions($article)->latest();Get Specific Version
Section titled “Get Specific Version”$revision = $article->getRevision(3); // Get version 3Filter by Action
Section titled “Filter by Action”use Cline\Tracer\Enums\RevisionAction;
$updates = $article->revisions() ->where('action', RevisionAction::Updated) ->get();Filter by Causer
Section titled “Filter by Causer”// Revisions made by a specific user$userRevisions = $article->revisions() ->where('causer_type', User::class) ->where('causer_id', $userId) ->get();Filter by Date Range
Section titled “Filter by Date Range”$recentRevisions = $article->revisions() ->where('created_at', '>=', now()->subDays(7)) ->get();Working with Revision Data
Section titled “Working with Revision Data”Access Changed Values
Section titled “Access Changed Values”$revision = $article->latestRevision();
// Get all old values$oldValues = $revision->old_values;// ['title' => 'Old Title']
// Get all new values$newValues = $revision->new_values;// ['title' => 'New Title']
// Check if specific attribute changedif ($revision->hasChangedAttribute('title')) { $oldTitle = $revision->getOldValue('title'); $newTitle = $revision->getNewValue('title');}Get Human-Readable Description
Section titled “Get Human-Readable Description”$descriptions = $revision->describe();// [// 'title' => 'Changed from "Old Title" to "New Title"',// 'status' => 'Set to "published"',// ]Access Metadata
Section titled “Access Metadata”$revision = $article->latestRevision();
// Who made the change$causer = $revision->causer; // User model (polymorphic)
// When$when = $revision->created_at;
// Action type$action = $revision->action; // RevisionAction enum
// Version number$version = $revision->version; // intReverting Changes
Section titled “Reverting Changes”Revert to Specific Version
Section titled “Revert to Specific Version”use Cline\Tracer\Tracer;
// Revert to version 3Tracer::revisions($article)->revertTo(3);
// Or via the TracerManager directlyTracer::revertTo($article, 3);Revert to Revision Model
Section titled “Revert to Revision Model”$targetRevision = $article->revisions()->where('version', 3)->first();Tracer::revisions($article)->revertTo($targetRevision);Revert by Revision ID
Section titled “Revert by Revision ID”Tracer::revisions($article)->revertTo('01HQ4XYZABC...'); // ULID/UUIDWhat Happens During Revert
Section titled “What Happens During Revert”- Tracer reconstructs the model state at the target revision
- Applies those values to the current model
- Saves the model (without tracking this save)
- Creates a new “reverted” revision recording the change
$article->revertToRevision(2);
$latest = $article->latestRevision();$latest->action; // RevisionAction::Reverted$latest->metadata; // ['reverted_to_version' => 2]Disabling Tracking Temporarily
Section titled “Disabling Tracking Temporarily”For a Specific Operation
Section titled “For a Specific Operation”use Cline\Tracer\Tracer;
Tracer::revisions($article)->withoutTracking(function () use ($article) { $article->update(['view_count' => $article->view_count + 1]);});Manual Control
Section titled “Manual Control”use Cline\Tracer\Tracer;
Tracer::revisions($article)->disableTracking();$article->update(['internal_notes' => 'Not tracked']);Tracer::revisions($article)->enableTracking();Disable for Instance Lifetime
Section titled “Disable for Instance Lifetime”use Cline\Tracer\Tracer;
$article = Article::find(1);Tracer::revisions($article)->disableTracking();
// All changes to this instance are untracked$article->update(['status' => 'archived']);$article->update(['title' => 'New Title']);Custom Causer Resolution
Section titled “Custom Causer Resolution”By default, Tracer uses the authenticated user as the causer via the AuthCauserResolver. You can create a custom resolver:
use Cline\Tracer\Contracts\CauserResolver;use Illuminate\Database\Eloquent\Model;
class CustomCauserResolver implements CauserResolver{ public function resolve(): ?Model { // Use system user for automated changes if (app()->runningInConsole()) { return User::where('email', 'system@example.com')->first(); }
// Use API token owner for API requests if (request()->bearerToken()) { return PersonalAccessToken::findToken(request()->bearerToken())?->tokenable; }
return auth()->user(); }}Register in config/tracer.php:
'causer_resolver' => CustomCauserResolver::class,Custom Diff Strategy Per Model
Section titled “Custom Diff Strategy Per Model”Configure via config/tracer.php:
'models' => [ App\Models\Article::class => [ 'revision_diff_strategy' => AttributeDiffStrategy::class, ],],Or at runtime:
use Cline\Tracer\Tracer;use Cline\Tracer\Strategies\Diff\AttributeDiffStrategy;
Tracer::configure(Article::class) ->revisionDiffStrategy(AttributeDiffStrategy::class);SoftDeletes Integration
Section titled “SoftDeletes Integration”Tracer automatically detects soft deletes:
use Illuminate\Database\Eloquent\SoftDeletes;
class Article extends Model implements Traceable{ use HasRevisions; use SoftDeletes;}| Operation | Recorded Action |
|---|---|
$article->delete() | deleted |
$article->forceDelete() | force_deleted |
$article->restore() | restored |
Performance Considerations
Section titled “Performance Considerations”Eager Loading
Section titled “Eager Loading”// Load revisions with articles$articles = Article::with('revisions')->get();
// Load only recent revisions$articles = Article::with(['revisions' => function ($query) { $query->where('created_at', '>=', now()->subMonth())->limit(10);}])->get();Indexing
Section titled “Indexing”The migrations include indexes on:
traceable_type+traceable_id(composite)versionactioncauser_type+causer_id(composite)created_at
Pruning Old Revisions
Section titled “Pruning Old Revisions”// Delete revisions older than 1 yearRevision::where('created_at', '<', now()->subYear())->delete();
// Keep only last 100 revisions per model$article->revisions() ->orderByDesc('version') ->skip(100) ->take(PHP_INT_MAX) ->delete();Events
Section titled “Events”Tracer dispatches events for each revision:
use Cline\Tracer\Events\RevisionCreated;
class RevisionListener{ public function handle(RevisionCreated $event): void { $revision = $event->revision; $model = $revision->traceable;
// Send notification, log to external system, etc. Log::info("Revision {$revision->version} created for {$model->getKey()}"); }}Register in EventServiceProvider:
protected $listen = [ RevisionCreated::class => [ RevisionListener::class, ],];Next Steps
Section titled “Next Steps”- Staged Changes - Add approval workflows
- Strategies - Customize how diffs are calculated
- Advanced Usage - Events, custom strategies, and more