Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Versionable Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require visualbuilder/versionable
    php artisan vendor:publish --provider="Visualbuilder\Versionable\ServiceProvider"
    php artisan migrate
    
  2. 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'];
    }
    
  3. 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
    

Where to Look First

  • Model Trait: Focus on the Versionable trait and its methods (versions, latestVersion, revertToVersion, etc.).
  • Version Model: Inspect \Visualbuilder\Versionable\Version for polymorphic user relationships and diff capabilities.
  • Configuration: Check config/versionable.php for global settings like max_versions or version_strategy.

Implementation Patterns

Core Workflows

  1. Version Creation:

    • Automatically triggered on save()/update() for models with the Versionable trait.
    • Uses auth()->user() to store the polymorphic user (e.g., Admin, Customer).
    • Example:
      $admin = Admin::find(1);
      auth()->login($admin);
      $post = Post::create(['title' => 'Test']); // Version created with $admin as user
      
  2. Version Retrieval:

    • Access versions via model methods:
      $post->versions;          // Collection of all versions
      $post->latestVersion;     // Latest version
      $post->versionAt('2023-01-01'); // Version at a specific time
      
    • Filter versions by user type:
      $adminVersions = $post->versions()->where('user_type', Admin::class)->get();
      
  3. Reversion:

    • Revert a model to a previous state:
      $post->revertToVersion(2); // Reverts to version 2
      
    • Revert without saving (for preview):
      $version = $post->versions()->first();
      $draft = $version->revertWithoutSaving();
      
  4. Diffing:

    • Compare two versions:
      $diff = $post->getVersion(1)->diff($post->getVersion(2));
      $diff->toHtml(); // Render as HTML
      

Integration Tips

  1. Multi-Guard Auth:

    • Ensure the authenticated user is set before versionable operations:
      auth()->guard('admin')->login($adminUser);
      $post->update(['title' => 'New Title']); // Version created with admin guard's user
      
  2. Custom Version Model:

    • Extend \Visualbuilder\Versionable\Version for additional fields:
      class CustomVersion extends Version
      {
          protected $casts = ['metadata' => 'json'];
      }
      
    • Assign to your model:
      class Post extends Model
      {
          use Versionable;
          public string $versionModel = CustomVersion::class;
      }
      
  3. Bulk Operations:

    • Use batch methods for cleanup:
      $post->removeVersions([1, 3, 5]); // Soft delete specific versions
      $post->forceRemoveAllVersions(); // Force delete all
      
  4. Temporary Disable:

    • Skip versioning for specific operations:
      Post::withoutVersion(function () {
          Post::create(['title' => 'Unversioned']);
      });
      
  5. Event Listeners:

    • Hook into version events (e.g., versioning or versioned):
      Version::created(function ($version) {
          // Log version creation
      });
      
  6. Filament Integration:

    • Use mansoorkhan96/filament-versionable for admin panel support:
      use MansoorKhan96\FilamentVersionable\FilamentVersionable;
      class PostResource extends Resource
      {
          use FilamentVersionable;
      }
      

Gotchas and Tips

Pitfalls

  1. Polymorphic User Mismatch:

    • If auth()->user() returns null, versions will lack a user. Ensure authentication is set before versionable operations.
    • Fix: Use auth()->guard('guard_name')->user() explicitly or validate user presence:
      if (!auth()->check()) {
          throw new \Exception('User not authenticated');
      }
      
  2. Attribute Whitelisting/Blacklisting:

    • Forgetting to define $versionable or $dontVersionable may cause all attributes to be versioned, bloating storage.
    • Tip: Start with a whitelist and expand as needed.
  3. Diff Strategy Overhead:

    • The default DIFF strategy reduces storage but may complicate reverts if only partial data is stored.
    • Tip: Use SNAPSHOT strategy for critical models where full data integrity is required.
  4. Migration Conflicts:

    • If the versions table already exists (e.g., from overtrue/laravel-versionable), the polymorphic migration may fail.
    • Fix: Drop the table or manually adjust the migration:
      Schema::table('versions', function (Blueprint $table) {
          $table->dropForeign(['user_id']);
          $table->dropColumn('user_id');
          $table->morphs('user');
      });
      
  5. Soft Deletes Interaction:

    • Soft-deleted models may still trigger versioning. Explicitly skip versioning for soft deletes:
      $post->delete(); // Soft delete (no version created)
      Post::withoutVersion(function () use ($post) {
          $post->forceDelete(); // Force delete (no version)
      });
      
  6. Performance with Large Version History:

    • Querying versions() on models with thousands of versions can be slow.
    • Tip: Limit results or use lazy loading:
      $post->versions()->take(100)->get();
      

Debugging Tips

  1. Check Version Creation:

    • Verify versions are created by inspecting the versions table or logging:
      Version::created(function ($version) {
          \Log::info('Version created', ['version_id' => $version->id]);
      });
      
  2. Validate User Polymorphism:

    • Ensure the user_type and user_id columns are correctly populated:
      $version = $post->latestVersion;
      dump($version->user); // Should return the authenticated user model
      
  3. Diff Debugging:

    • If diffs appear empty, ensure the attributes are versionable and the versions contain data:
      $diff = $post->getVersion(1)->diff($post->getVersion(2));
      dump($diff->toArray()); // Inspect raw diff data
      
  4. Event Hooks:

    • Use event listeners to debug versioning behavior:
      Version::creating(function ($version) {
          \Log::debug('Creating version', ['model' => $version->versionable]);
      });
      

Extension Points

  1. Custom Version Model:

    • Extend \Visualbuilder\Versionable\Version to add metadata or custom logic:
      class AuditVersion extends Version
      {
          protected $fillable = ['notes', 'ip_address'];
      }
      
  2. Version Strategy:

    • Override the default DIFF strategy for specific models:
      class Post extends Model
      {
          use Versionable;
          protected $versionStrategy = VersionStrategy::SNAPSHOT;
      }
      
  3. Versionable Attributes Dynamic:

    • Dynamically set versionable attributes based on context:
      class Post extends Model
      {
          use Versionable;
          protected $versionable = [];
      
          public function setVersionableAttributes()
          {
              $this->versionable = ['title', 'content'];
              if ($this->isDraft()) {
                  $this->versionable[] = 'draft_notes';
              }
          }
      }
      
  4. Custom Diff Renderer:

    • Extend the diff output format by creating a custom renderer:
      class CustomDiffRenderer
      {
          public static function render(array $diff): string
          {
              return "Custom: " . print_r($diff, true);
          }
      }
      
  5. Version Cleanup:

    • Add a scheduled task to prune old versions:
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky