cosmira / sandbox
Draft and publish database-backed Laravel configuration safely.
Requires
- php: ^8.2
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/database: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
Requires (Dev)
- dragon-code/benchmark: ^4.5
- infection/infection: ^0.32.4
- laravel/pint: ^1.27
- orchestra/testbench: ^10.0 || ^11.0
- phpunit/phpunit: ^11.0 || ^12.0
- rector/rector: ^2.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-16 09:55:07 UTC
README
Database-backed drafts for Laravel configuration screens.
Edit configuration through Eloquent shadow tables, then publish, discard, or save the draft. One user owns the editing session. Active data remains available while the owner works on pricing rules, category trees, feature flags, or other shared configuration.
Requirements
- PHP 8.2 or later.
- Laravel 12 or 13, with a PHP version supported by that Laravel release.
- SQLite, PostgreSQL, or MySQL.
- An active table and a matching draft table for each registered model or table.
See upgrading from 0.1.0 when updating an existing integration.
Installation
composer require cosmira/sandbox:^0.1 php artisan vendor:publish --tag=sandbox-migrations php artisan migrate
Publish migrations only for a new installation. For an existing shared status schema, follow the installation guide.
Create matching shadow tables in your application's migrations. For example,
categories uses categories_sb by default. Sandbox does not create those tables.
Quick start
Add HasSandbox to each configuration model:
use Cosmira\Sandbox\HasSandbox; use Illuminate\Database\Eloquent\Model; class Category extends Model { use HasSandbox; public $timestamps = false; protected $table = 'categories'; }
Register models in an application service provider, in dependency order:
use App\Models\Category; use Cosmira\Sandbox\Facades\Sandbox; Sandbox::models(Category::class);
Protect configuration routes with authentication and the sandbox middleware:
use App\Http\Controllers\CategoryController; use Illuminate\Support\Facades\Route; Route::middleware(['auth', 'sandbox'])->group(function (): void { Route::resource('categories', CategoryController::class); });
Controllers keep using normal Eloquent queries. The first mutating request copies active configuration into the draft and locks it for the current user. The application's own authorization rules still determine who may edit.
Finish the session explicitly:
use Cosmira\Sandbox\Facades\Sandbox; Sandbox::me()->commit(note: 'Publish configuration'); // Or discard changes: Sandbox::me()->rollback(note: 'Discard draft'); // Or keep the draft for later: Sandbox::me()->save(note: 'Continue tomorrow');
Lifecycle
| Operation | Result |
|---|---|
open() on a free sandbox |
Copy active data into the draft and lock it |
open() on a saved draft |
Resume editing without resetting changes |
commit() |
Publish the draft and release the lock |
rollback() |
Replace the draft with active data and release the lock |
save() |
Keep the draft and pause editing |
The owner reads and writes a locked draft. Other users read active data and cannot modify the locked configuration. Authenticated users can read a saved draft; guests always read active data. Host permissions apply to every operation.
An edit includes opening, locking, and writes in one transaction. Exceptions and HTTP responses with status 400 or higher roll it back. Completed lifecycle events run after commit, so a listener failure cannot undo published data.
Documentation
- Configuration workflow and request handling
- Lifecycle API
- Model registration
- Pivot tables, trees, and selective copying
- Model options and explicit table scopes
- Synchronization
- Status and UI
- Events
- Testing helpers
- Changelog
Development
composer install
composer test
composer test:coverage
composer test:mutation
composer test:types
composer test:rector
npx --yes markdownlint-cli2@0.23.2
CI runs the PHP/Laravel matrix on Ubuntu and dedicated Windows concurrency checks, plus PostgreSQL and MySQL contracts. Separate workflows check Composer metadata, PHPStan, Rector, Soda, coverage, mutations, spelling, ShellCheck, and Markdown. Pint commits formatting fixes on pushes and checks pull requests. Statement coverage must reach 80%; Infection requires 96% MSI and covered MSI.
soda.php combines Soda's standard rules with multiline PHPDoc checks for all
methods, properties, and constants in src, regardless of visibility. Promoted
properties can be documented with a matching constructor @param tag.
CI installs Soda separately so Sandbox keeps supporting PHP 8.2. With a local
Soda checkout, run:
php /path/to/soda/soda quality src --config=soda.php
Limitations
- One global configuration session per application.
- Model table selection belongs to the application context; concurrent coroutine runtimes sharing that context are unsupported.
- Hydrated models retain their selected table after a context ends. Do not carry draft models across authorization boundaries.
- Raw SQL and
DB::table()calls do not switch tables automatically. - Oracle native integration is unverified. A custom backend must preserve the enclosing transaction and enforce the same lifecycle contract.
License
MIT.