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

Revisionable Laravel Package

sofa/revisionable

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require sofa/revisionable
    
  2. Register Provider: Add to config/app.php:

    'providers' => [
        // ...
        Sofa\Revisionable\Laravel\ServiceProvider::class,
    ],
    
  3. Publish Config:

    php artisan vendor:publish --provider="Sofa\Revisionable\Laravel\ServiceProvider"
    

    This generates config/sofa_revisionable.php with default settings.

  4. Enable Revisions on Model: Use the Revisionable trait in your Eloquent model:

    use Sofa\Revisionable\RevisionableTrait;
    
    class Post extends Model
    {
        use RevisionableTrait;
    }
    

First Use Case

View all revisions for a model instance:

$post = Post::find(1);
$revisions = $post->revisions; // Collection of Revision models

Compare two revisions:

$revision1 = $post->revisions->first();
$revision2 = $post->revisions->last();
$diff = $revision1->diff($revision2); // Returns a diff array

Implementation Patterns

Core Workflows

1. Tracking Changes

  • Automatic Tracking: The package automatically tracks created_at, updated_at, and all modified fields (including soft-deletes if enabled).
  • Manual Field Control: Exclude fields from tracking via revisionable array in your model:
    class Post extends Model
    {
        use RevisionableTrait;
    
        protected $revisionable = [
            'title',
            'content',
            '-votes', // Exclude 'votes' field
        ];
    }
    

2. Querying Revisions

  • Fetch revisions for a model:
    $revisions = Post::find(1)->revisions;
    
  • Filter revisions by date or user:
    $revisions = Post::find(1)->revisions()
        ->where('created_at', '>', now()->subDays(7))
        ->where('user_id', auth()->id())
        ->get();
    
  • Get a specific revision:
    $revision = Post::find(1)->revisions()->find(5); // Revision ID 5
    

3. Restoring Revisions

  • Restore a model to a previous state:
    $post = Post::find(1);
    $post->restoreRevision($revisionId); // Restores to the state of revision ID
    
  • Rollback to a specific revision:
    $post->rollbackRevision($revisionId);
    

4. Bulk Operations

  • Compare two revisions in bulk:
    $diff = $revision1->diff($revision2);
    // Returns an array like:
    // [
    //     'title' => ['old' => 'Old Title', 'new' => 'New Title'],
    //     'content' => ['old' => 'Old Content', 'new' => 'New Content'],
    // ]
    

5. Integration with Events

  • Listen for revision events:
    // In EventServiceProvider
    protected $listen = [
        'Sofa\Revisionable\Events\RevisionCreated' => [
            'App\Listeners\LogRevision',
        ],
    ];
    

Advanced Patterns

1. Custom Revision Model

  • Extend the default Revision model:
    class CustomRevision extends \Sofa\Revisionable\Models\Revision
    {
        protected $table = 'custom_revisions';
    }
    
  • Update the config:
    'model' => \App\Models\CustomRevision::class,
    

2. Soft Deletes Integration

  • Enable soft deletes for revisions:
    use Sofa\Revisionable\RevisionableTrait;
    use Illuminate\Database\Eloquent\SoftDeletes;
    
    class Post extends Model
    {
        use RevisionableTrait, SoftDeletes;
    
        protected $revisionable = ['title', 'content'];
    }
    
  • Configure in sofa_revisionable.php:
    'soft_deletes' => true,
    

3. Customizing Revision Data

  • Add metadata to revisions via revisionable trait hooks:
    class Post extends Model
    {
        use RevisionableTrait;
    
        protected static function bootRevisionableTrait()
        {
            static::revisionable(function ($model, $revision) {
                $revision->ip_address = request()->ip();
            });
        }
    }
    

4. API Endpoints for Revisions

  • Create a resource controller to expose revisions via API:
    class RevisionController extends Controller
    {
        public function index(Post $post)
        {
            return $post->revisions;
        }
    
        public function show(Post $post, Revision $revision)
        {
            return $revision;
        }
    }
    

5. Testing Revisions

  • Mock revisions in tests:
    $post = Post::factory()->create();
    $post->revisions()->create([
        'data' => ['title' => 'Old Title'],
        'user_id' => 1,
    ]);
    

Gotchas and Tips

Common Pitfalls

1. Field Exclusion Misconfiguration

  • Issue: Fields not being tracked despite being in $revisionable. Fix: Ensure the field exists in the database and is not excluded with a - prefix.
    // Correct:
    protected $revisionable = ['title', 'content'];
    
    // Incorrect (excludes 'votes'):
    protected $revisionable = ['title', '-votes'];
    

2. Performance with Large Revisions

  • Issue: Slow queries when fetching many revisions. Fix: Use with() to eager-load relationships or paginate:
    $revisions = Post::find(1)->revisions()->paginate(10);
    

3. Soft Deletes Conflicts

  • Issue: Soft-deleted models may not trigger revisions. Fix: Ensure soft_deletes is enabled in both the model and config:
    'soft_deletes' => true,
    

4. Custom Primary Key

  • Issue: Revisions not saving if model uses a non-integer primary key. Fix: Explicitly set the primary key in the config:
    'primary_key' => 'uuid',
    

5. Timestamps Override

  • Issue: created_at and updated_at not being tracked. Fix: Ensure $timestamps = true in your model and include them in $revisionable:
    protected $revisionable = ['*']; // Tracks all fields including timestamps
    

Debugging Tips

1. Check Revision Data

  • Dump the raw revision data to debug:
    $revision = Post::find(1)->revisions()->first();
    dd($revision->data); // Inspect stored data
    

2. Enable Query Logging

  • Log SQL queries to identify issues:
    DB::enableQueryLog();
    $revisions = Post::find(1)->revisions;
    dd(DB::getQueryLog());
    

3. Verify Middleware

  • Ensure the RevisionableMiddleware is registered if using custom guards:
    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],
    

4. Check for Hook Conflicts

  • If revisions aren’t saving, verify no other hooks are interfering:
    protected static function bootRevisionableTrait()
    {
        static::saving(function ($model) {
            // Ensure no conflicting logic here
        });
    }
    

Extension Points

1. Custom Revision Storage

  • Store revisions in a different database or service by extending the Revision model and overriding the save() method.

2. Revision Validation

  • Add validation logic before saving a revision:
    class Post extends Model
    {
        use RevisionableTrait;
    
        protected static function bootRevisionableTrait()
        {
            static::revisionable(function ($model, $revision) {
                if ($model->isSignificantChange()) {
                    $revision->validate();
                }
            });
        }
    }
    

3. Revision Notifications

  • Trigger notifications when revisions are created:
    class Post extends Model
    {
        use RevisionableTrait;
    
        protected static function bootRevisionableTrait()
        {
            static::revisionable(function ($model, $revision) {
                Notification::send($model->user, new RevisionCreated($revision));
            });
        }
    }
    

4. Revision Indexing

  • Optimize queries by adding
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.
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed