visualbuilder/versionable
Laravel model versioning with polymorphic user support. Track and store a history of changes across multiple user types/guards, keep a set number of versions, whitelist/blacklist attributes, record only diffs, and easily revert models to any saved version.
Installation:
composer require visualbuilder/versionable
php artisan vendor:publish --provider="Visualbuilder\Versionable\ServiceProvider"
php artisan migrate
Apply Trait to Model:
Add use Visualbuilder\Versionable\Versionable; and define $versionable attributes (whitelist) or $dontVersionable (blacklist) in your Eloquent model:
class Post extends Model
{
use Versionable;
protected $versionable = ['title', 'content'];
}
First Use Case: Create/update a versionable model to auto-generate versions with the authenticated user (polymorphic):
$post = Post::create(['title' => 'Draft', 'content' => 'Initial content']);
$post->update(['title' => 'Updated']); // Creates a new version
Versionable trait and its methods (versions, latestVersion, revertToVersion, etc.).\Visualbuilder\Versionable\Version for polymorphic user relationships and diff capabilities.config/versionable.php for global settings like max_versions or version_strategy.Version Creation:
save()/update() for models with the Versionable trait.auth()->user() to store the polymorphic user (e.g., Admin, Customer).$admin = Admin::find(1);
auth()->login($admin);
$post = Post::create(['title' => 'Test']); // Version created with $admin as user
Version Retrieval:
$post->versions; // Collection of all versions
$post->latestVersion; // Latest version
$post->versionAt('2023-01-01'); // Version at a specific time
$adminVersions = $post->versions()->where('user_type', Admin::class)->get();
Reversion:
$post->revertToVersion(2); // Reverts to version 2
$version = $post->versions()->first();
$draft = $version->revertWithoutSaving();
Diffing:
$diff = $post->getVersion(1)->diff($post->getVersion(2));
$diff->toHtml(); // Render as HTML
Multi-Guard Auth:
auth()->guard('admin')->login($adminUser);
$post->update(['title' => 'New Title']); // Version created with admin guard's user
Custom Version Model:
\Visualbuilder\Versionable\Version for additional fields:
class CustomVersion extends Version
{
protected $casts = ['metadata' => 'json'];
}
class Post extends Model
{
use Versionable;
public string $versionModel = CustomVersion::class;
}
Bulk Operations:
$post->removeVersions([1, 3, 5]); // Soft delete specific versions
$post->forceRemoveAllVersions(); // Force delete all
Temporary Disable:
Post::withoutVersion(function () {
Post::create(['title' => 'Unversioned']);
});
Event Listeners:
versioning or versioned):
Version::created(function ($version) {
// Log version creation
});
Filament Integration:
mansoorkhan96/filament-versionable for admin panel support:
use MansoorKhan96\FilamentVersionable\FilamentVersionable;
class PostResource extends Resource
{
use FilamentVersionable;
}
Polymorphic User Mismatch:
auth()->user() returns null, versions will lack a user. Ensure authentication is set before versionable operations.auth()->guard('guard_name')->user() explicitly or validate user presence:
if (!auth()->check()) {
throw new \Exception('User not authenticated');
}
Attribute Whitelisting/Blacklisting:
$versionable or $dontVersionable may cause all attributes to be versioned, bloating storage.Diff Strategy Overhead:
DIFF strategy reduces storage but may complicate reverts if only partial data is stored.SNAPSHOT strategy for critical models where full data integrity is required.Migration Conflicts:
versions table already exists (e.g., from overtrue/laravel-versionable), the polymorphic migration may fail.Schema::table('versions', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
$table->morphs('user');
});
Soft Deletes Interaction:
$post->delete(); // Soft delete (no version created)
Post::withoutVersion(function () use ($post) {
$post->forceDelete(); // Force delete (no version)
});
Performance with Large Version History:
versions() on models with thousands of versions can be slow.$post->versions()->take(100)->get();
Check Version Creation:
versions table or logging:
Version::created(function ($version) {
\Log::info('Version created', ['version_id' => $version->id]);
});
Validate User Polymorphism:
user_type and user_id columns are correctly populated:
$version = $post->latestVersion;
dump($version->user); // Should return the authenticated user model
Diff Debugging:
$diff = $post->getVersion(1)->diff($post->getVersion(2));
dump($diff->toArray()); // Inspect raw diff data
Event Hooks:
Version::creating(function ($version) {
\Log::debug('Creating version', ['model' => $version->versionable]);
});
Custom Version Model:
\Visualbuilder\Versionable\Version to add metadata or custom logic:
class AuditVersion extends Version
{
protected $fillable = ['notes', 'ip_address'];
}
Version Strategy:
DIFF strategy for specific models:
class Post extends Model
{
use Versionable;
protected $versionStrategy = VersionStrategy::SNAPSHOT;
}
Versionable Attributes Dynamic:
class Post extends Model
{
use Versionable;
protected $versionable = [];
public function setVersionableAttributes()
{
$this->versionable = ['title', 'content'];
if ($this->isDraft()) {
$this->versionable[] = 'draft_notes';
}
}
}
Custom Diff Renderer:
class CustomDiffRenderer
{
public static function render(array $diff): string
{
return "Custom: " . print_r($diff, true);
}
}
Version Cleanup:
How can I help you explore Laravel packages today?