avocet-shores/laravel-rewind
Full version control for Eloquent models: rewind, fast-forward, restore, diff, and query point-in-time state. Uses a hybrid engine (diffs + snapshots) with configurable intervals, thread-safe locking, batch revisions, queued writes, and pruning.
Installation:
composer require avocet-shores/laravel-rewind
php artisan vendor:publish --provider="AvocetShores\LaravelRewind\LaravelRewindServiceProvider"
php artisan migrate
Run the migration to add the current_version column to your models.
Enable Versioning:
Add the Rewindable trait to your Eloquent model:
use AvocetShores\LaravelRewind\Traits\Rewindable;
class Post extends Model
{
use Rewindable;
}
First Use Case: Update a model and immediately test versioning:
$post = Post::find(1);
$post->update(['title' => 'Updated Title']);
Rewind::rewind($post); // Revert to previous state
AvocetShores\LaravelRewind\Facades\Rewind for core operations.Rewindable for model integration.RewindVersion for querying history.config/rewind.php for customization.Versioning Models:
// Automatically tracks changes
$post->update(['title' => 'New Title']);
// Manual version creation (if needed)
$post->createVersion();
Navigation:
// Move backward/forward
Rewind::rewind($post, 2); // Go back 2 versions
Rewind::fastForward($post); // Move forward 1 version
// Jump to a specific version
Rewind::goTo($post, 5);
State Restoration:
// Preview without audit trail
Rewind::goTo($post, 3);
// Create a new version (audit-compliant)
Rewind::restore($post, 3);
Diffing and Inspection:
// Get diff between versions
$diff = Rewind::diff($post, 1, 5);
// Replay history
Rewind::replay($post, 1, 10, function ($version, $attributes) {
// Process each version
});
State Transitions:
Track critical fields (e.g., status) separately:
class Order extends Model
{
use Rewindable;
protected array $rewindStateFields = ['status'];
}
Query transitions:
$order->versions()->whereStateBecame('status', 'shipped')->get();
Batch Versioning: Group related changes (e.g., order + items):
$batchUuid = Rewind::batch(function () {
$order->update(['status' => 'shipped']);
$item->update(['shipped_at' => now()]);
});
Exclude Fields:
public static function excludedFromVersioning(): array
{
return ['password', 'api_token'];
}
Metadata: Attach context to versions:
Rewind::withMeta(['reason' => 'Bulk update', 'ticket' => 'JIRA-123']);
$post->update(['title' => 'Updated']);
Preview Changes:
Use goTo() to inspect old states without creating new versions.
Audit Trails:
Use restore() for compliance-critical changes.
Performance:
listener_should_queue = true).snapshot_interval (default: 10) to balance storage vs. reconstruction speed.Testing: Reset versions in tests:
$post->versions()->delete();
Locking Conflicts:
on_lock_timeout config (log, event, or throw).throw for queued listeners to trigger retries.Snapshot Interval:
snapshot_interval) = slower reconstruction.RewindVersion size and adjust (e.g., snapshot_interval = 20).Amend vs. Exclude:
amendCurrentVersion() folds changes into the current version (no new row).excludedFromVersioning() removes fields entirely from history.State Transitions:
$rewindStateFields are tracked as transitions.stateHistory('field') to debug transition logic.Pruning:
keep and days values don’t conflict.--pretend first to preview deletions.Version Reconstruction:
If goTo() or diff() returns unexpected data:
snapshot_interval in config.Rewind::getVersionAttributes($post, $version);
Lock Timeouts:
throw in config to surface issues:
'on_lock_timeout' => 'throw',
LockTimeoutRewindException.Batch Queries:
batch_uuid is consistent across related models:
RewindVersion::inBatch($batchUuid)->get();
Custom Version Model:
Extend RewindVersion for additional fields:
class CustomRewindVersion extends RewindVersion
{
protected $casts = [
'custom_field' => 'string',
];
}
Update config/rewind.php:
'version_model' => App\Models\CustomRewindVersion::class,
Event Listeners: Listen for version creation:
RewindVersion::created(function ($version) {
// Log or notify on critical changes
});
Queue Retries: Customize retry logic for queued versions:
'queue' => [
'retry_after' => 60, // seconds
'max_attempts' => 3,
],
Indexing:
Add indexes to RewindVersion for frequent queries:
Schema::table('rewind_versions', function (Blueprint $table) {
$table->index('model_type');
$table->index('model_id');
$table->index(['model_type', 'model_id', 'version']);
});
Pruning Schedule: Run pruning during off-peak hours:
Schedule::command('rewind:prune --keep=50 --force')->dailyAt('2:00');
Snapshot Optimization:
For models with large attributes, increase snapshot_interval (e.g., 30).
Default Values:
max_versions: Global cap (per-model overrides take precedence).snapshot_interval: Default 10; adjust based on model complexity.Queue Config:
listener_should_queue = true, ensure your queue worker is running.failed_jobs table for stuck jobs.State Fields:
$rewindStateFields trigger transition queries.whereStateChanged('field') to debug missing transitions.How can I help you explore Laravel packages today?