Installation:
composer require mpociot/versionable
php artisan vendor:publish --provider="Mpociot\Versionable\Providers\ServiceProvider" --tag="migrations"
php artisan migrate
versions table (e.g., add user_id for soft-deletes or reasons for versioning triggers).Enable Versioning:
Use the Versionable trait in your model:
use Mpociot\Versionable\Traits\Versionable;
class Post extends Model
{
use Versionable;
}
First Use Case:
$post = new Post(['title' => 'Hello World']);
$post->save(); // Creates version 1
$versions = $post->versions()->count(); // Returns 1
Versioning Triggers:
save(), update(), or delete() (configurable via versionableEvents).
protected $versionableEvents = [
'creating', 'updating', 'deleting', 'restoring'
];
$post->createVersion(); // Force-create a version
Restoring Models:
$post->versions()->find(1)->revert(); // Restores version 1
$oldPost = $post->versions()->find(1)->getModel();
$post->restore($oldPost); // Merges changes from version 1
Querying Versions:
$versions = $post->versions()->with('user')->get();
$post->versions()->where('created_at', '>', now()->subDays(7))->get();
Soft-Deletes Integration:
versions table:
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use Versionable, SoftDeletes;
}
$post->versions()->onlyTrashed()->restore();
Custom Version Attributes:
changed_by):
protected $versionableAttributes = [
'title', 'content', 'changed_by'
];
versionable.created or versionable.restored:
event(new VersionableCreated($post, $version));
class PostObserver extends ModelObserver {
public function saving($post) {
if ($post->isDirty('content')) {
$post->setVersionableAttribute('content_reviewed', false);
}
}
}
Route::get('/posts/{post}/versions', function (Post $post) {
return $post->versions()->with('user')->get();
});
Performance:
$versionableAttributes to limit fields.versions table for model_id, created_at:
Schema::table('versions', function (Blueprint $table) {
$table->index(['model_id', 'created_at']);
});
withTrashed() sparingly.Data Conflicts:
DB::transaction(function () use ($post, $oldPost) {
$post->restore($oldPost);
});
Post → Comment → Post).Configuration Quirks:
versionableEvents is empty, no versions are created. Defaults to ['creating', 'updating'].created_at/updated_at are not excluded from versioning if relying on them for auditing.Missing Versions:
versionableEvents and model observers for early save() calls.versionableAttributes includes critical fields (e.g., title but not updated_at).Restoration Issues:
getModel() to inspect the version’s state before restoring:
$oldData = $post->versions()->find(1)->getModel()->toArray();
$fillable/$guarded).Migration Conflicts:
--force:
php artisan vendor:publish --tag="migrations" --force
Custom Version Logic:
createVersion() to add pre/post hooks:
protected function createVersion(array $options = [])
{
$this->fireModelEvent('versioning', false);
parent::createVersion($options);
}
Version model to add custom methods (e.g., diff()):
public function diff(Version $other)
{
return array_diff($this->data, $other->data);
}
Version Storage:
versionableStorage to switch to a custom storage driver (e.g., Redis):
protected $versionableStorage = 'redis';
Mpociot\Versionable\Contracts\VersionableStorage for custom backends.UI Integration:
spatie/laravel-medialibrary for versioned file attachments:
$post->versions()->where('model_type', Post::class)->with('attachments')->get();
How can I help you explore Laravel packages today?