Drop a Revertvel snapshot before any risky Laravel operation — revert it anytime.

Maintainers

Package info

github.com/AbdelilahEzzouini/revertvel

pkg:composer/abdelilah-ezzouini/revertvel

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-06-04 10:08 UTC

This package is auto-updated.

Last update: 2026-06-04 10:08:29 UTC


README

Drop a Revertvel snapshot before any risky Laravel operation — revert it anytime with a single command.

Instead of manually writing rollback scripts after something goes wrong, you define the undo logic before you run the operation. you just run php artisan revertvel:revert {id}.

Installation

composer require abdelilah-ezzouini/revertvel

Laravel auto-discovers the service provider. No manual registration needed.

Publish the config (optional)

php artisan vendor:publish --tag=revertvel-config

Run the migration

php artisan migrate

This creates the revertvel_snapshots table.

Configuration

// config/revertvel.php
return [
    'table' => 'revertvel_snapshots', // change if it conflicts with an existing table
];

Usage

1. Capture a snapshot before your risky operation

Wrap the revert logic (not the forward logic) in Revertvel::capture().
The closure you pass is what will run if you ever need to undo.

Critical — always use fully-qualified class names (FQCN) inside the closure.

The closure is serialized to a string and stored in the database. When it is later unserialized and executed, PHP has no knowledge of the use ClassName imports that existed in your original file. Any bare alias like DB or Carbon will cause a fatal "Class not found" error at revert time.

\Illuminate\Support\Facades\DB::table(...)
DB::table(...) — will fail at revert time

Also — never capture Eloquent model instances or class objects via use().
Serialize them to plain arrays first with ->toArray(). Primitives and arrays travel safely; objects may not deserialize correctly across requests.

use AbdelilahEzzouini\Revertvel\Facades\Revertvel;
use Illuminate\Support\Facades\DB;

// 1. Collect the state you need to restore — as plain arrays, not objects
$originalRows = DB::table('orders')
    ->where('status', 'pending')
    ->get()
    ->map(fn($r) => (array) $r)
    ->all();

// 2. Capture the revert closure — use FQCN for every class inside
$snapshotId = Revertvel::capture('Bulk close pending orders', function () use ($originalRows) {
    \Illuminate\Support\Facades\DB::transaction(function () use ($originalRows) {
        foreach ($originalRows as $row) {
            \Illuminate\Support\Facades\DB::table('orders')
                ->where('id', $row['id'])
                ->update(['status' => $row['status']]);
        }
    });
});

// 3. Now perform the risky forward operation
DB::table('orders')->where('status', 'pending')->update(['status' => 'closed']);

$this->info("Done. Revert anytime: php artisan revertvel:revert {$snapshotId}");

Tip: Call Revertvel::capture() inside the same DB::transaction() as your forward operation.
If the transaction rolls back, the snapshot is also discarded — no orphaned records.

DB::transaction(function () use (&$snapshotId) {
    $snapshotId = Revertvel::capture('label', function () {
        // ⚠️ Use FQCN here — no class aliases available at revert time
        \Illuminate\Support\Facades\DB::table('...')->update([...]);
    });

    // ... your forward operation ...
});

2. List snapshots

# All snapshots (latest 20)
php artisan revertvel:list

# Only pending (not yet reverted)
php artisan revertvel:list --pending

# Only reverted
php artisan revertvel:list --reverted

# Increase limit
php artisan revertvel:list --limit=50

3. Revert a snapshot

php artisan revertvel:revert 7

The command shows a summary and asks for confirmation. Use --force to skip the prompt:

php artisan revertvel:revert 7 --force

Reverting a snapshot that was already reverted shows a warning but can be re-run with --force.

Programmatic revert

use AbdelilahEzzouini\Revertvel\Facades\Revertvel;

Revertvel::revert(7);

How it works

  1. You pass a plain PHP Closure to Revertvel::capture().
  2. The package serializes it using laravel/serializable-closure and stores it as a longText column in the database — all captured variables travel with it.
  3. revertvel:revert unserializes and calls the closure. No handler classes, no external dependencies, no class naming required.
  4. Why FQCN? The closure is stored as a serialized string. When deserialized in a future request or CLI run, PHP re-evaluates the closure body in a blank context — the use Illuminate\... import statements from your original file are gone. Only fully-qualified names like \Illuminate\Support\Facades\DB are guaranteed to resolve.

AI Rules / Copilot Instructions

Publish a ready-made rule file for your AI coding assistant (GitHub Copilot, Cursor, etc.) so it always generates correct Revertvel::capture() closures:

php artisan vendor:publish --tag=revertvel-ai-rules

This drops .github/copilot-instructions/revertvel.md into your project. GitHub Copilot picks it up automatically; for Cursor, move or symlink it to .cursor/rules/revertvel.md.

The rules file instructs the AI to:

  • Always use FQCN inside every capture closure
  • Never capture Eloquent objects — only plain arrays
  • Always wrap revert logic in \Illuminate\Support\Facades\DB::transaction()
  • Always call capture() inside the same transaction as the forward operation

Running tests

Inside the package directory:

composer install
./vendor/bin/phpunit

Changelog

v1.0.0

  • Initial release

License

MIT